Lesson 5
Stacks and Queues in Java
Stacks and Queues in Java

Welcome to an exciting exploration of two fundamental data structures in Java: Stacks and Queues! These data structures organize and manage data effectively. Specifically, stacks are comparable to a pile of plates, while queues are akin to standing in line. Let's dive in!

Stacks: Last In, First Out (LIFO)

A stack follows the "Last In, First Out" or LIFO principle. Imagine a stack of plates where the last plate added is the first one to be removed. In Java, we can use ArrayDeque<E> or LinkedList<E> to implement a stack, with push for inserting (pushing) and pop for removing (popping, returning the element that is removed).

  • Push (Insertion): Adds an element to the top of the stack. This makes the newly added element the last-in, which is the first to be removed when needed.
  • Pop (Removal): Removes and returns the element at the top of the stack, following the Last In, First Out (LIFO) principle.

Let's explore this with an example of a stack of plates.

Java
1import java.util.ArrayDeque; 2import java.util.Deque; 3 4class StackOfPlates { 5 private Deque<String> stack; 6 7 public StackOfPlates() { 8 stack = new ArrayDeque<>(); 9 } 10 11 // Insert a plate at the top of the stack 12 public void addPlate(String plate) { 13 stack.push(plate); 14 } 15 16 // Remove the top plate from the stack 17 public String removePlate() { 18 if (stack.isEmpty()) { 19 return "No plates left to remove!"; 20 } 21 return stack.pop(); 22 } 23} 24 25public class Solution { 26 public static void main(String[] args) { 27 StackOfPlates plates = new StackOfPlates(); 28 plates.addPlate("Plate"); // Pushing a plate 29 plates.addPlate("Another Plate"); // Pushing another plate 30 // Let's remove a plate; it should be the last one we added. 31 System.out.println("Removed: " + plates.removePlate()); // Outputs: Removed: Another Plate 32 } 33}

The last plate added was removed first, demonstrating the LIFO property of a stack.

Queues: First In, First Out (FIFO)

A queue operates on the "First In, First Out" or FIFO principle, similar to waiting in line. In Java, we can implement a queue using the Queue<E> interface with a LinkedList or ArrayDeque, where add (enqueue) inserts at the end and remove or poll (dequeue) removes from the front.

  • Add (Enqueue): Adds an element to the end of the queue, following the First In, First Out (FIFO) order.
  • Poll (Dequeue): Removes and returns the element at the front of the queue. This is the oldest element, maintaining the FIFO order.

Let's examine this with a queue of people.

Java
1import java.util.LinkedList; 2import java.util.Queue; 3 4class QueueOfPeople { 5 private Queue<String> queue; 6 7 public QueueOfPeople() { 8 queue = new LinkedList<>(); 9 } 10 11 // Add a person to the end of the queue 12 public void enqueuePerson(String person) { 13 queue.add(person); 14 } 15 16 // Remove the first person from the queue (who has been waiting the longest) 17 public String dequeuePerson() { 18 if (queue.isEmpty()) { 19 return "No people left to dequeue!"; 20 } 21 return queue.poll(); 22 } 23} 24 25public class Solution { 26 public static void main(String[] args) { 27 QueueOfPeople people = new QueueOfPeople(); 28 people.enqueuePerson("Person 1"); // Person 1 enters the queue 29 people.enqueuePerson("Person 2"); // Person 2 arrives after Person 1 30 // Who's next in line? It must be Person 1! 31 System.out.println("Removed: " + people.dequeuePerson()); // Outputs: Removed: Person 1 32 } 33}

Here, Person 1, the first to join the queue, left before Person 2, demonstrating the FIFO property of a queue.

Mastering Stack and Queue Operations with a Class

Stacks efficiently handle ordered data, much like a web browser's history. Queues are optimal when the order of arrival is essential, such as in a store queue.

Let's illustrate using a text editor that features an Undo mechanism (a Stack) and a Print Queue.

  • Undo Mechanism (Stack):
    • The makeChange(String action) method pushes each action onto the stack, simulating the latest edit. When an undo is performed using undoChange(), it removes this most recent change from the stack, just as pressing "Undo" would revert the last action. The stack’s LIFO property ensures that the latest change is always the first undone.
  • Print Queue (Queue):
    • The addToPrint(String doc) method enqueues documents for printing, adding each one to the end of the queue. When printDoc() is called, it dequeues the document at the front, following the order of addition. This FIFO process simulates a print queue, where the earliest document in line is printed first.
Java
1import java.util.ArrayDeque; 2import java.util.Deque; 3import java.util.LinkedList; 4import java.util.Queue; 5 6class TextEditor { 7 private Deque<String> stack; 8 private Queue<String> queue; 9 10 public TextEditor() { 11 stack = new ArrayDeque<>(); 12 queue = new LinkedList<>(); 13 } 14 15 // Make a change (e.g., edit text, insert image, change font) 16 public void makeChange(String action) { 17 stack.push(action); // Add to the stack of actions 18 } 19 20 // Undo the most recent change 21 public String undoChange() { 22 if (stack.isEmpty()) { 23 return "No changes to undo!"; 24 } 25 return stack.pop(); // Remove the last action from the stack 26 } 27 28 // Add a document to the queue for printing 29 public void addToPrint(String doc) { 30 queue.add(doc); 31 } 32 33 // Print the document that has been waiting the longest 34 public String printDoc() { 35 if (queue.isEmpty()) { 36 return "No documents in print queue!"; 37 } 38 return queue.poll(); // Remove the first document from the queue 39 } 40} 41 42public class Solution { 43 public static void main(String[] args) { 44 TextEditor editor = new TextEditor(); 45 editor.makeChange("Changed font size"); // Make a change 46 editor.makeChange("Inserted image"); // Make another change 47 // Let's undo a change. It should be the last change we made. 48 System.out.println("Undo: " + editor.undoChange()); // Undo: Inserted image 49 50 editor.addToPrint("Proposal.docx"); // Queue a document for printing 51 editor.addToPrint("Report.xlsx"); // Queue another document 52 // Let's print a document. It should be the document that has been waiting the longest. 53 System.out.println("Print: " + editor.printDoc()); // Print: Proposal.docx 54 } 55}

This code reintroduces the concepts of a Stack (Undo feature) and Queue (Print queue) in practical scenarios.

Why ArrayDeque for Stack and LinkedList for Queue?

When choosing the right data structure for stacks and queues in Java, ArrayDeque and LinkedList offer distinct advantages tailored to their respective operations.

For implementing a stack, ArrayDeque is often the preferred choice. This is because:

  • ArrayDeque is optimized for stack operations: It allows fast, constant-time push and pop operations at the start of the deque. Additionally, ArrayDeque is more efficient than Java's legacy Stack class, which is synchronized and incurs unnecessary overhead when used in single-threaded contexts.
  • Memory Efficiency: ArrayDeque uses a contiguous array, which avoids the structural overhead associated with LinkedList, and dynamically grows as needed, optimizing memory usage.

When implementing a queue, LinkedList is particularly well-suited because:

  • LinkedList provides efficient insertion and removal: It efficiently handles operations at both ends, making it ideal for queues where elements are added at the end and removed from the front. This allows LinkedList to manage elements without the need for shifting, which can be a hindrance in array-based structures.
  • Flexibility: LinkedList implements the Queue interface, providing versatility as a queue structure with minimal setup and allowing developers to leverage its native methods seamlessly.
Lesson Summary

Great work! You have explored the mechanics of Stacks and Queues, both integral data structures in Java. Remember to practice what you've learned. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.