Heretofore (can you believe that’s a word?), we’ve seen a direct relationship between ADTs and their implementing DSs, but we’ll add a twist to that mix today…

Let’s talk about another data structure that we’ve actually mentioned many times before, but can formalize now…

Question

What is the call stack, and separately, if we had to think about as a data structure / collection, what would be its hallmarks / properties / operations?

It turns out that the Call Stack gets its name from another ADT!

Definition

A stack is an ADT used to store elements in a way that the most recently stored items are the first to be retrieved (FILO: First-in, Last-out).

Remark

FILO Intuition: The most recent thing we insert into a stack is the first thing we get back. Useful for certain scenarios wherein we care about the order in which items in a collection are stored.

Trust me, it’s cool! By analogy, think of a pile of dishes:

  • You stack dishes one on top of the other
  • Once you’ve stacked dish Z on top of all the other dishes, you can only access dish Z (and cannot access any other dish beneath it) until you pop Z off the top again

Stacks are very useful; in fact, there’s a reason why local variables are stored in the stack, because every function’s variables get stacked on the top when they’re called, and popped off again when you leave the function!

Example

List some other examples of why you might use the FILO behavior of a Stack.

  • Retracing your steps in a maze so that you can backtrack and explore every path; moves most recently taken are near the top.
  • Some aspects of parsing code, such as determining which parentheses match with what other parentheses in some mathematical expression.
  • Some forms of state management in computer graphics, such as applying some style like a background color to a bunch of things before removing that style.

Stack Operations

Stacks support a few basic operations:

  • push(toPush); adds the given element to the top of the stack.
  • peek(); returns the element at the top of the stack.
  • pop(); removes the element at the top of the stack.

Stack Implementation

Toolkit

Stacks can actually be implemented using either a LinkedList or an ArrayList!

Since we’re mostly familiar with ArrayLists, let’s challenge ourselves by trying to use a LinkedList to represent a Stack:

Question

OK, Andrew, why would we want to make a data structure that’s a linked list… but more restricted than one?

With this description in mind, let’s try implementing our own IntStack using the IntLinkedList class we made last time!

Question

Since we’ve done all of that work in the LinkedList class anyways, what class-design mechanism could we use to properly implement the Stack behavior?

// Remark: Since this package is becoming more than just
// lists, we'd probably rename it to something better, but
// we'll keep it as-is for now just for illustrative purposes
package intlist;
 
// [?] This inheritance "works," but is it a good idea?
public class IntStack extends IntLinkedList {
 
    IntStack () {
        super();
    }
 
    public void push (int toPush) {
        super.prepend(toPush);
    }
 
    public void pop () {
        super.removeAt(0);
    }
 
    public int peek () {
        return super.getAt(0);
    }
 
}

Testing it, we see:

package intlist;
 
public class IntStackTests {
 
    public static void main (String[] args) {
        IntStack stacky = new IntStack();
        stacky.push(1);
        stacky.push(2);
        stacky.push(3);
        stacky.pop();
        // [?] What gets printed here?
        System.out.println(stacky.peek());
    }
 
}

Question

What’s wrong with our inheritance-structured implementation of

To illustrate problem 1: note that the following still compiles and runs:

package intlist;
 
public class IntStackTests {
 
    public static void main (String[] args) {
        IntStack stacky = new IntStack();
        // A stack with prepend?! Odd, but kinda OK...
        stacky.prepend(1);
        stacky.push(2);
        stacky.push(3);
        // A stack with removeAt?!?! Not OK!
        stacky.removeAt(1);
        System.out.println(stacky.peek());
    }
 
}

Question

Why is it, based on the operations we described for a Stack above, not OK for one to support the removeAt(index) method?

Sometimes we desire this flexibility, and is how the Java Stack is implemented (see JavaDocs and section below), but other times, we want to ensure that only those methods that we want exposed are available to the user.

Question

What tool that we’ve learned about can help us restrict a Stack’s operations to only those intended, even if we’ve implemented it using a LinkedList?

To thus limit our IntStacks to only pop, push, and peek, we could define a new interface that IntStack implements:

package intlist;
 
public interface TrueIntStack {
 
    public void push (int toPush);
    public void pop ();
    public int peek ();
 
}
 
// [!] Not shown: IntStack implementing TrueIntStack

Of course, implementing an interface merely specifies the minimal methods that must be implemented, and does not inherently prevent others (including those inherited) from being made available as well.

To restrict IntStacks to these 3 methods, we would change our tests such that:

package intlist;
 
public class IntStackTests {
 
    public static void main (String[] args) {
        // [!] Note new declared "type" specifies the interface,
        // which is OK because the constructor on the RHS is an
        // object whose class implements it
        TrueIntStack stacky = new IntStack();
        // [X] Now: this is a compilation error (as we wanted)
        stacky.prepend(1);
        stacky.push(2);
        stacky.push(3);
        // [X] This too is a compilation error (as we wanted)
        stacky.removeAt(0);
        System.out.println(stacky.peek());
    }
 
}

A couple of parting remarks from this example:

Remark

A better approach (solving problem 2) for implementing a Stack: implement its methods in the IntLinkedList class, have IntLinkedList implement TrueIntStack, and then simply create objects using the TrueIntStack interface, e.g., TrueIntStack s = new IntLinkedList();

Definition

Note: we could have just as easily maintained a stack as an ArrayList, whereby we pushed and popped items from the back of the list. The choice to implement one as an array vs. linked list will depend heavily on the intended application.

Example

Try implementing an IntStack above using our IntArrayList from the previous lectures; it’ll be pretty simple as well!


Queues

Queues aren’t a whole lot different from stacks, except that instead of pushing to and popping from the top, we queue to the back of the queue and dequeue from the front.

Definition

A queue is an ADT that maintains the FIFO (first in, first out) behavior of insertion and retrieval of its elements.

Remark

FIFO Intuition: The most recent thing we insert into a stack is the last thing we get back. Useful for certain scenarios wherein we care about the order in which items in a collection are stored.

There’s a reason that “queues” in supermarkets and theme parks are named so; people enter them at the back, and then are served at the front!

Example

What are some examples of scenarios in which a Queue might be useful?

  • Maintaining an ordered sequence of actions that some agent should carry out, like moving “Up”, then “Right”, then “Up” in a grid maze in sequence.
  • Maintaining some time-specified order of admittance, like order in which users of a site can begin ordering tickets or an online game’s server / lobby.

Here’s what that might look like as a singly linked list:

Again, though, you should be aware that a queue is an abstract data type, and that some implementations WILL allow you to access the intermediary nodes behind the front.

Remark

Reflect: why would an ArrayList not be as good of an implementation choice for a Queue as a LinkedList is?

Common Queue Functions:

  • push(toQueue) (sometimes called enqueue) adds an element to the back of the queue.
  • poll() (sometimes generally referred to as pop() or dequeue()) returns and removes the element from the front of the queue.
  • peek() returns a reference to the element at the front of the queue (least recently added)

Example

Implementing a Queue is left as an exercise, since they’re easily maintained with a LinkedList (which, coincidentally, will be one of your upcoming homeworks heh).


Java Collections Framework

Toolkit

The JCF Stack provides an implementation for FILO behavior using a Vector (basically, an ArrayList) (though also recommended is the Deque data structure, which we won’t discuss herein).

The Stack interface can be viewed here.

Stack<String> stacky = new Stack<>();
stacky.add("a");
stacky.add("b");
stacky.add("c");
// pop removes and returns top of stack
stacky.pop();
 
// peek returns top of stack
// [?] What gets printed here?
System.out.println(stacky.peek());

Debug

Warning: because all Queue methods are already implemented using LinkedLists, the Java Queue is simply an interface, not an instantiable class!

As such, we can either initialize a Queue as a Linked List or some other structures we have yet to learn about.

The Queue interface can be viewed here.

Queue being an interface allows us to specify how we want that to be done, as long as certain implementations define the Queue interface’s methods (in the link above) for example:

// Queue (Interface) implemented with LinkedList
Queue<String> q = new LinkedList<String>();
// add pushes an item to the back of the Queue
q.add("a");
q.add("b");
q.add("c");
// poll dequeues and returns item at front
q.poll();
 
// peek returns item at front
// [?] What gets printed here?
System.out.println(q.peek());