Skip to content

Programming by Design

If you're not prepared to be wrong, you'll never come up with anything original. – Sir Ken Robinson

  • About
  • Java-PbD
  • C-PbD
  • ASM-PbD
  • Algorithms
  • Other

Chapter 16 – Stack, Queues and Deques

Posted on June 2, 2019January 14, 2025 By William Jojo
Java Book

(Updated January 14, 2025)

Table of contents

    Overview
    Stacks
    Queues
    Deques (Double-Ended Queues)
    Exercises

Overview

This section expands linked lists. Remember that each data structure shown here is still a linked list. What changes are the rules governing how the object may be manipulated.


Stacks

Overview

Stacks are a wondrous structure. This is mainly because they are nothing more than a linked list. Well, if they are nothing more than a linked list, why do we call them stacks? The underlying structure is a linked list, but how we enforce its use makes it a stack.

For example, let us say we need to call a series of methods, which will call more methods, and those methods call even more, and so on. When a method calls a method that calls another method, we need a way to return to the previous method and then to the previous method before that. Of course, we think of the return statement, and all is well. The ability to call a method and later use a return statement only frees us of the knowledge and responsibility of stack management for so long.

Imagine ourselves on the cafeteria line or at our favorite buffet. As we enter the line at the end, we form a queue (see Queues discussion). And the queue rules define how we enter at the back of the line and exit at the front. (There are no such rules for governing a generic linked list.) We come upon a pile of plates, or rather a stack of plates. Each person in the line (queue) takes a plate from the top.

Now imagine that someone is washing the plates in the kitchen. This person starts with no plates, and the rapid-cleaning dishwasher has many cleaned plates. The person at this station then removes plates from the dishwasher and places them one atop another.

It is these actions that define the purpose and use of a stack, which we will now describe.

Stack Operations

Recall that a stack is nothing more than a linked list. The actions we allow on this linked list are what make it special. To place an object on the stack is to push onto the stack. To remove an object from the stack is to pop the stack. All push and pop actions may only occur at the top of the stack. We may inspect the value at the top of the stack without actually popping it off. Any actions within the stack are prohibited.

Important Note!
These are just the traditional basic rules of a stack. This does not preclude someone from allowing other operations and still calling it a stack.

Using our linked list code as a foundation, we will now define a stack in Java. Example 1a has some basic stack code.

IntStack.java
import java.util.EmptyStackException;

public class IntStack {
    // the top of the stack
    private Node top;
    private int size;

    private class Node {
        private int data;
        private Node next;

        private Node(int item) {
            data = item;
            next = null;
        }
    }

    public IntStack() {
        top = null;
        size = 0;
    }

    // returns the top value or exception when empty.
    public int peek() {
        if (isEmpty())
            throw new EmptyStackException();
        else
            return top.data;
    }

    // push new item on the top
    public void push(int item) {
        Node n = new Node(item);
        n.next = top;
        top = n;
        size++;
    }

    // remove item from the top and return the removed value.
    public int pop() {
        int v;
        if (isEmpty())
            throw new EmptyStackException();
        v = top.data;
        top = top.next;
        size--;
        return v;
    }

    // debug method to dump the contents of the stack to the screen
    public String toString() {
        Node p = top;
        String s = "";

        while (p != null) {
            s = s + p.data + "\n";
            p = p.next;
        }
        return s;
    }

    // method to determine if the stack is empty.
    public boolean isEmpty() {
        return top == null;
    }

    // method to return the number of frames
    public int size() {
        return size;
    }

    public static void main(String[] args) {
        IntStack s = new IntStack();

        s.push(7);
        s.push(5);
        s.push(3);
        System.out.println("Stack length is " + s.size());
        System.out.println("Stack contains:\n" + s);

        while (!s.isEmpty()) {
            System.out.println("Popped " + s.pop());
        }

        // intentionally try to pop an empty stack
        try {
            System.out.println("Popped " + s.pop());
        } catch (EmptyStackException e) {
            System.out.println("That could have been bad!");
        }
    }
}

Example 1: The class IntStack which uses a Node to perform typical stack operations.

Notice how we build the stack. Everything is pushed onto the top (or head from our linked list discussions) of the chain. Subsequently, we only pop from the top. This is all we need to perform the necessary operations on a stack.

But do not forget this is just a linked list governed by different rules.

The pop() method must be sensitive to the possibility that the stack may be empty. This is called a stack underflow, but Java already has an exception for this purpose. The pop() method will throw an EmptyStackException in the event we attempt to pop an empty stack. This would allow an application using this class to catch the exception and have an opportunity to decide what to do next.

When pushing a value onto the stack, there is the possibility of running out of memory and not being able to add another value. Ordinarily, we would call this a stack overflow, but since the actual issue is that we have exhausted memory, we have chosen not to deal with this at all. Instead, it is left to the programmer to decide what to do if the application exceeds available memory.

We also introduce a couple of other tools for use with stacks. For example, we may want to look at the value on top of the stack but not pop it. This is useful in determining if some action should be taken based on the top stack value. A tool like this is more advantageous than popping the value, inspecting it to determine the action taken, and then pushing it back onto the stack if we do not want it. The peek() method allows us to inspect the value on top, being mindful of the possibility that the stack is empty and throwing an exception when appropriate.

Another helpful tool for stack operations is a debug method to dump the stack to the screen for viewing. Sometimes, this, and a debugger, helps ascertain why a program performs a certain way. The dumpstack() method from our linked list has been renamed toString() and returns the contents of our integer stack as a String. The first item displayed is the top; all subsequent values are shown below.

One more useful tool is the boolean method isEmpty(). The isEmpty() method is useful when trying to set up a while loop to deal with all of the items on the stack.

Finally, we can get the number of stack elements (also called frames) by calling the size() method.

Stacks are used in many everyday pieces of software, including compilers, operating systems, and applications. Anytime we need the ability to keep historical information as last-in-first-out (LIFO or FILO – first-in-last-out), a stack is likely a good candidate.

The code in Example 1 contains a main() method to exercise the IntStack class. Essentially, all the test code will perform the same job – add a few values and try all code paths along the way.

The output is shown below:

Stack length is 3
Stack contains:
3
5
7

Popped 3
Popped 5
Popped 7
That could have been bad!

Queues

Queues are a data structure with all the basic properties of a linked list, but now we keep track of both ends instead of just the front (top, head, first, etc.).

Stacks are first-in-last-out (FILO or LIFO) structures, whereas queues are first-in-first-out (FIFO). It is exactly like going to the grocery store, where we enter at the end of the line and work our way toward the front until it is our turn.

Similar to Stacks that have push() and pop() methods, the operations are now called enqueue and dequeue. The corresponding methods are enq() and deq() respectively.

The rules are that we always enqueue on the end and (last) and dequeue from the front (first)

IntQueue.java
import java.util.NoSuchElementException;

public class IntQueue {
    // the front and back of the queue
    private Node first, last;
    private int size;

    private class Node {
        private int data;
        private Node next;

        private Node(int item) {
            data = item;
            next = null;
        }
    }

    public IntQueue() {
        first = null;
        last = null;
        size = 0;
    }

    // returns the first value or exception when empty.
    public int peek() {
        if (isEmpty())
            throw new NoSuchElementException();
        else
            return first.data;
    }

    // enqueue (add) new item on the last
    public void enq(int item) {
        Node oldlast = last;

        last = new Node(item);
        if (isEmpty())
            first = last;
        else
            oldlast.next = last;
        size++;
    }

    // dequeue (remove) item from the first of the queue and return the removed value.
    public int deq() {
        int v;

        if (isEmpty())
            throw new NoSuchElementException();
        v = first.data;
        first = first.next;
        if (first == null)
            last = null;
        size--;
        return v;
    }

    // debug method to dump the contents of the queue to the screen
    public String toString() {
        Node p = first;
        String s;

        s = "FIRST ->";
        while (p != null) {
            s = s + " " + p.data + " ";
            p = p.next;
        }
        s = s + "<- LAST\n";
        return s;
    }

    // method to determine if the queue is empty.
    public boolean isEmpty() {
        return first == null;
    }

    // method to return the number of queue entries
    public int size() {
        return size;
    }

    public static void main(String[] args) {
        IntQueue q = new IntQueue();

        q.enq(7);
        q.enq(5);
        q.enq(3);
        System.out.print("Queue contains:\n" + q);
        System.out.println("Queue length is " + q.size());

        while (!q.isEmpty()) {
            System.out.println("Dequeued " + q.deq());
        }

        // intentionally try to deq an empty queue
        try {
            System.out.println("Dequeued " + q.deq());
        } catch (NoSuchElementException e) {
            System.out.println("That could have been bad!");
        }
    }
}

Example 2: The class IntQueue which uses a Node to perform typical queue operations.

Example 2 provides a main() method to test the IntQueue data structure.

The essential components are similar to the Stack example where we have a peek() method to non-destructively view the front of the line (first) as well as a nice toString() to dump the contents for debugging.

There is also a size() and isEmpty() method.

The output of the test is shown below:

Queue contains:
FIRST -> 7  5  3 <- LAST
Queue length is 3
Dequeued 7
Dequeued 5
Dequeued 3
That could have been bad!

Deques (Double-Ended Queues)

The double-ended queue or deque (pronounced deck) is what you get when you cross a queue with a schizophrenic stack. It is a simple modification of the queue in the previous section. The rules change enough to allow us to enqueue or dequeue from both ends, making it a first-or-last-in-first-or-last-out (FLIFLO) design.

There seems to be little agreement on what to call the functions (push and pop for stacks, enqueue, and dequeue for queues). We will call them addFirst(), addLast(), removeFirst() and removeLast().

Everything else will remain the same regarding the support methods (toString(), isEmpty(), size()).

Essentially the old enq() becomes addLast() and deq() becomes removeFirst(). The other two will be new. What will also be new here is the concept of the doubly-linked list. The base Node will be modified to be able to point to the next node but also point to the previous node. This is necessary since we can perform movement and relinking operations in both directions.

IntDeque.java
import java.util.NoSuchElementException;

public class IntDeque {
    // the top of the queue
    private Node first, last;
    private int size;

    private class Node {
        private int data;
        private Node next, prev;

        private Node(int item) {
            data = item;
            next = null;
            prev = null;
        }
    }

    public IntDeque() {
        first = null;
        last = null;
        size = 0;
    }

    // returns the first value or exception when empty.
    public int peekFirst() {
        if (isEmpty())
            throw new NoSuchElementException();
        else
            return first.data;
    }

    // returns the last value or exception when empty.
    public int peekLast() {
        if (isEmpty())
            throw new NoSuchElementException();
        else
            return last.data;
    }

    // enqueue (add) new item on the last
    public void addLast(int item) {
        Node oldlast = last;

        last = new Node(item);
        if (isEmpty())
            first = last;
        else {
            // link up both directions
            oldlast.next = last;
            last.prev = oldlast;
        }
        size++;
    }

    // enqueue (add) new item on the first
    public void addFirst(int item) {
        Node oldfirst = first;

        first = new Node(item);
        if (isEmpty())
            last = first;
        else {
            // link up both directions
            oldfirst.prev = first;
            first.next = oldfirst;
        }
        size++;
    }

    // dequeue (remove) item from the first of the queue and return the removed value.
    public int removeFirst() {
        int v;

        if (isEmpty())
            throw new NoSuchElementException();
        v = first.data;
        // advance and null the prev link.
        first = first.next;
        if (first != null)
            first.prev = null;
        else
            last = null;
        size--;
        return v;
    }

    // dequeue (remove) item from the first of the queue and return the removed value.
    public int removeLast() {
        int v;

        if (isEmpty())
            throw new NoSuchElementException();
        v = last.data;
        // advance and null the prev link.
        last = last.prev;
        if (last != null)
            last.next = null;
        else
            first = null;
        size--;
        return v;
    }

    // debug method to dump the contents of the queue to the screen
    public String toString() {
        Node p = first;
        String s;

        s = "FIRST ->";
        while (p != null) {
            s = s + " " + p.data + " ";
            p = p.next;
        }
        s = s + "<- LAST\n";

        s = s + "LAST ->";
        p = last;
        while (p != null) {
            s = s + " " + p.data + " ";
            p = p.prev;
        }
        s = s + "<- FIRST\n";

        return s;
    }

    // method to determine if the queue is empty.
    public boolean isEmpty() {
        return first == null;
    }

    // method to return the number of queue entries
    public int size() {
        return size;
    }

    public static void main(String[] args) {
        IntDeque d = new IntDeque();
        int x;

        d.addLast(7);
        d.addLast(5);
        d.addLast(3);
        d.addFirst(13);
        d.addFirst(15);
        d.addFirst(17);
        System.out.print("Deque contains:\n" + d);
        System.out.println("Deque length is " + d.size());

        x = 0;
        while (x < 3) {
            System.out.println("Dequeued from front " + d.removeFirst());
            x++;
        }

        System.out.print("Deque contains:\n" + d);

        x = 0;
        while (x < 3) {
            System.out.println("Dequeued from end " + d.removeLast());
            x++;
        }

        System.out.print("Deque contains:\n" + d);

        // intentionally try to deq an empty deqque
        try {
            System.out.println("Dequeued from front " + d.removeFirst());
        } catch (NoSuchElementException e) {
            System.out.println("That could have been bad!");
        }

        // intentionally try to deq an empty deque
        try {
            System.out.println("Dequeued fron end " + d.removeLast());
        } catch (NoSuchElementException e) {
            System.out.println("That could have been bad!");
        }
    }
}

Example 3: The class IntDeque which uses a Node to perform typical deque operations.

Example 3 tests the IntDeque class.

The output is shown below:

Deque contains:
FIRST -> 17  15  13  7  5  3 <- LAST
LAST -> 3  5  7  13  15  17 <- FIRST
Deque length is 6
Dequeued from front 17
Dequeued from front 15
Dequeued from front 13
Deque contains:
FIRST -> 7  5  3 <- LAST
LAST -> 3  5  7 <- FIRST
Dequeued from end 3
Dequeued from end 5
Dequeued from end 7
Deque contains:
FIRST -><- LAST
LAST -><- FIRST
That could have been bad!
That could have been bad!

Exercises

    (Beginner) Write a method for each data structure that will return the Stack, Queue, or Deque as an array of int.
    (Intermediate) Convert each data structure to use String for the primary data type.
    (Advanced)

Post navigation

❮ Previous Post: Chapter 15 – Hashing
Next Post: Chapter 17 – Generic Methods and Classes ❯

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Copyright © 2018 – 2025 Programming by Design.