Stack, queue and linked list
Problem
"Undo" undoes the wrong thing
A text editor keeps the actions in a list. Ctrl+Z should undo the last one, but it undoes the first:
List<String> actions = new ArrayList<>();actions.add("Typed text");actions.add("Made the title bold");actions.add("Inserted a photo");
// Ctrl+Z: undo the last action.String undone = actions.remove(0);System.out.println("Undone: " + undone);System.out.println("Left: " + actions);Undone: Typed textLeft: [Made the title bold, Inserted a photo]The fix is actions.remove(actions.size() - 1). But everyone who touches this code has to remember which end to remove from. And at the store checkout it is the other way around: the first one who came is served first. Is there a collection that knows the end by itself?
The idea
Stack: the last one on top
A stack is like a pile of plates. You put on top and you take from the top. The last thing put in comes out first. This is called LIFO: last in, first out.
The "Back" button in the browser is a stack:
Deque<String> history = new ArrayDeque<>();history.push("Home");history.push("Laptops");history.push("14-inch laptop");System.out.println("Now: " + history.peek());
history.pop();System.out.println("Back: " + history.peek());System.out.println(history);Now: 14-inch laptopBack: Laptops[Laptops, Home]pushputs on top,popremoves and returns the top one, andpeekonly looks at the top one.Dequeis the interface, andArrayDequeis the class. Both are injava.util.printlnprints the stack from the top down.
More precisely, if you are curious
popon an empty stack stops the program withjava.util.NoSuchElementException.peekon an empty stack returnsnull.- Java also has an older
Stackclass. It works, but the Java documentation recommendsArrayDeque:
Stack<String> stack = new Stack<>();stack.push("Google");stack.push("Apple");stack.push("Microsoft");System.out.println("On top: " + stack.peek());while (!stack.isEmpty()) { System.out.println("Popped: " + stack.pop());}On top: MicrosoftPopped: MicrosoftPopped: ApplePopped: GoogleTry it
What is left to undo?
What will the program print?
Deque<String> undo = new ArrayDeque<>();undo.push("font");undo.push("color");undo.push("size");undo.pop();undo.push("underline");while (!undo.isEmpty()) { System.out.println(undo.pop());}pop removes "size", because it is on top. Then "underline" goes on top. The loop takes them out from the top down: underline, color, font.
The idea
Queue: first come, first served
A queue is like the line at the checkout. You join at the back, and the first one is served. This is called FIFO: first in, first out.
Print jobs wait in a queue:
Queue<String> printer = new ArrayDeque<>();printer.offer("invoice.pdf");printer.offer("contract.docx");printer.offer("photo.jpg");System.out.println("Next: " + printer.peek());
while (!printer.isEmpty()) { System.out.println("Printing " + printer.poll());}System.out.println("Queue: " + printer);Next: invoice.pdfPrinting invoice.pdfPrinting contract.docxPrinting photo.jpgQueue: []offerputs at the back,pollremoves and returns the first one, andpeeklooks at the first one.- The type is the
Queueinterface, and the class isArrayDequeagain. It can do both: be a stack and be a queue. The name comes from double-ended queue, a queue with two ends. pollandpeekon an empty queue returnnull.
More precisely, if you are curious
- There are also
add,remove()andelement(). They do the same, but on an empty queue they throw an exception instead of returningnull. - In older examples the queue is a
LinkedList. That works too, becauseLinkedListalso implementsQueue:
Queue<String> clients = new LinkedList<>();clients.offer("Customer 1");clients.offer("Customer 2");System.out.println("Served: " + clients.poll());System.out.println("Waiting: " + clients);Served: Customer 1Waiting: [Customer 2]ArrayDequedoes not acceptnullas an element. Otherwisepollwould have no way to say "it is empty".
Try it
Next at the checkout
The cashier calls the next customer. Which method takes them from the queue?
Queue<String> checkout = new ArrayDeque<>();
checkout.offer("Ivan");
checkout.offer("Maria");
String next = checkout.();
System.out.println("Your turn, " + next);poll removes and returns the first one in the queue, here Ivan. peek would only look, without removing him, and next time it would be Ivan again.
See it
A linked list inside
ArrayList keeps the elements next to each other in an array. That is why inserting at the front moves all the others. There is another way: each element is in a separate object that holds the value and an arrow to the next one. An object like this is called a node:
class Node { String value; Node next;
Node(String value, Node next) { this.value = value; this.next = next; }}A list of nodes is called a linked list. It is enough to keep the first node, head. You find each next one by following the next arrow. The last one points to null.
Node head = null;head = new Node("Coffee", head);head = new Node("Milk", head);head = new Node("Bread", head);
for (Node n = head; n != null; n = n.next) { System.out.println(n.value);}Step by step, in words
- The list is empty: head points to null.
- A new node: next is the old head (null), and head moves to the node.
- The second node points to the first. Nothing moves in memory: only the arrows change.
- The third node also goes in at the front: the last one added is first.
- n starts at head and prints each node.
- n reaches null: the loop stops.
- Inserting at the front: a new node whose
nextpoints to the old first one, andheadpoints to the new one. Two arrows, and you move nothing. - Looping: you start at
headand follownextuntil you reachnull. - The cost: nodes have no indexes. You reach the fifth element by going through the first four.
The idea
LinkedList, and which one when
Java has a ready-made linked list: LinkedList. It implements both List and Deque, so it has add, get and remove, but also addFirst, addLast and getFirst:
LinkedList<String> route = new LinkedList<>();route.add("Plovdiv");route.add("Stara Zagora");route.addFirst("Sofia");route.addLast("Burgas");System.out.println(route);
route.remove("Stara Zagora");System.out.println("After the change: " + route);System.out.println("From " + route.getFirst() + " to " + route.getLast());[Sofia, Plovdiv, Stara Zagora, Burgas]After the change: [Sofia, Plovdiv, Burgas]From Sofia to BurgasWhich one when:
- A list by number:
ArrayList. This is the usual choice. - A stack or a queue:
ArrayDeque. LinkedList: rarely. It works for both, but it is usually slower than they are.
More precisely, if you are curious
- Why does
LinkedListlose, even at inserting? Each node is a separate object somewhere in memory, and the processor reads data fastest when it sits side by side. The array behindArrayListandArrayDequeis exactly that. In practice,LinkedListwins only if you often insert or remove in the middle through an iterator that has already got there. LinkedListkeeps arrows in both directions, to the next one and to the previous one. That is whygetLastandremoveLastare fast too. The node from this card has onlynext. A list like that is called singly linked.
Try it
Find the bug
The method should count the nodes. For the list Bread → Milk → Coffee it returns 2 instead of 3. And for an empty list it stops with NullPointerException. Which line has the bug?
Pick the line with the bug.
n.next != null stops when n is on the last node, before counting it. And if head is null, there is nowhere to read n.next from. The condition should be n != null: go on while there is a node.
Exercise
Stacks and queues at work
private field of the class.1. A toy train. A Train class with the methods addWagon(String wagon) and removeWagon(). The train is a stack: the last car coupled is the first one uncoupled. removeWagon() returns the uncoupled car, or null if there is none. Add count(). Couple three cars and uncouple two.
Uncoupled: Car 3Uncoupled: Car 2Cars: 12. A bus route. A BusRoute class with a queue of stops and the methods addStop(String stop) and getNextStop(), which returns the next stop, or null if the route is over. Add "Central Station", "NDK" and "Studentski Grad", and go through the route.
Next stop: Central StationNext stop: NDKNext stop: Studentski GradEnd of the route.3. A stock ticker. A StockTicker class keeps the last N prices of one stock. The constructor gets the symbol and N. updatePrice(double price) adds a price, and if there are more than N prices, it removes the oldest one. Add getLastPrice() and average() for the stored prices. For SAP with N = 3, pass the prices 150, 152, 149 and 155. Hint: Deque has peekLast().
SAP: last price €155.0Average of the last 3: €152.04. Balanced brackets. A code editor checks whether the brackets (), [] and {} are closed in the right order. Write isBalanced(String text) with a stack: at an opening bracket you put it on the stack, and at a closing one you remove the top one and check that it is the same kind. At the end the stack must be empty. Read three lines.
(1 + [2 * 3])(1 + 2]((5)(1 + [2 * 3]) -> yes(1 + 2] -> no((5) -> no5. Your own linked list. A MyLinkedList class with the nodes from the lesson and the methods add(String value) (at the end), remove(String value), contains(String value), size() and toString(). In remove, find the node before the one you are looking for and redirect its next. Check the case where the first one is removed separately.
[Invoice, Report, Meeting]Contains Report: trueAfter removal: [Invoice, Meeting]Size: 2More tasks
- Undo and Redo. Keep two stacks:
undoandredo. A new action goes intoundoand clearsredo. "Undo" moves the top one fromundotoredo, and "Redo" moves it back. - Words in reverse. Read a sentence and print it with the words in reverse order, going through a stack: "Today is Friday" becomes "Friday is Today".
- Two checkouts. Customers come one by one and go to the checkout with the shorter line. Print who was served at which checkout.
- Reverse the linked list. Add a
reverse()method toMyLinkedListthat turns the arrows around without making new nodes.
Test