Lecture 19: Implementing Stacks

Announcements

We’re almost to the end. Hang in there!

There’s a gap in the grading – the grading for homework 13 has been delayed, but it will be up soon.

Academic honesty: I did a preliminary check on a few of the projects the other day and the results were…disappointing. If you are maybe suddenly realizing that you worked a little too closely with someone on a programming assignment, please send me an email and detail the circumstances. I may still want to talk to you, but I will definitely think better of you if you proactively reach out to me before I prepare the academic honesty cases.

Continuing Specialized linear ADTs

Priority queues

A priority queue is like a queue, but it returns the next “smallest” (in Java) thing, rather than the first-in thing, when remove or peek is called.

It’s important to note that the exact order of the items stored in the priority queue is not visible to the user; you can only see the “next” / “top” item (that will be returned by peek or remove). Internally, priority queues are implemented as “heaps”, which are a tree-based structure similar to, but different from, the binary search trees we talked about briefly earlier this semester. Heaps allow for efficient (log n) insertion and removal of the smallest item.

How do we define “smallest”? The usual way, by either depending upon the “natural” ordering of the elements stored in the PriorityQueue<E> (that is, they must implement Comparable) or by passing in a Comparator when constructing the PriorityQueue.

Suppose then we do the following with a priority queue:

pq.add("b");
pq.add("a");
pq.remove();
pq.add("c");
pq.add("d");
pq.peek();

In class exercise

pq.add(3);
pq.add(2);
pq.add(1);
pq.peek()
pq.remove();
pq.add(1);
pq.remove();
pq.remove();

Implementing a stack

The stack, a last-in, first-out data structure. How might we go about building one? Today we’ll build two implementations of a stack in class (time permitting), based upon the ADT specified by a Java interface.

As we go through this, see how well you can follow along. This is the first of several “barometer” tasks we’ll be doing: if you find them easy (or at least, doable), then you should feel reasonable optimistic about 187 next semester. If you find them difficult or confusing, again, maybe have a fallback plan (or plan to study hard over the winter break, perhaps practicing on 186 assignments you had trouble with).

The interface

package stack;

public interface Stack<E> {
	void push(E e) throws StackOverflowException;
	E pop() throws StackUnderflowException;
	E peek() throws StackUnderflowException;
	boolean isEmpty();
	boolean isFull();
	int size();
}

This is a minimal interface; of course you can add more if you like. Note we include thrown exceptions, which is optional depending upon whether the exceptions are “checked” or “unchecked”; the former inherit from Exception and the latter from RuntimeException. See https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html for more details.

We’ll trivially define (empty) classes for each exception ourselves. (in Eclipse)

Array-based stack

Now let’s look at an array-based implementation of a stack. We’ll do a “bounded” stack, that is, a stack that has a fixed maximum capacity. (You can imagine an unbounded, growable array, like the one we talked about for lists earlier in the semester, but we’ll skip that for now.)

What other state, that is, instance variables, does the stack require, other than an array? An index into that array, that indicates where the top of the stack “lives”. You have some discretion here – it could index the current top of the stack, or it could index the spot where the next top will be. This is a stylistic choice, but I find pointing it to the current top (or -1, if the stack is empty) to be more natural. Up to you.

(on board)

package stack;

public class BoundedArrayStack<E> implements Stack<E> {
	private E[] array;
	private int top;	
	
	public BoundedArrayStack(int capacity) {
		top = -1;
		array = (E[]) new Object[capacity]; // references to this array must not escape this class
	}
	
	@Override
	public void push(E e) throws StackOverflowException {
		if (isFull()) {
			throw new StackOverflowException();
		}
		top += 1;
		array[top] = e;
	}

	@Override
	public E pop() throws StackUnderflowException {
		if (isEmpty()) {
			throw new StackUnderflowException();
		}
		E temp = array[top];
		array[top] = null;
		top--;
		return temp;
	}

	@Override
	public E peek() throws StackUnderflowException {
		if (isEmpty()) {
			throw new StackUnderflowException();
		}
		return array[top];
	}

	@Override
	public boolean isEmpty() {
		return top == -1;
	}

	@Override
	public int size() {
		return top + 1;
	}

	@Override
	public boolean isFull() {
		return top == array.length - 1;
	}

}

Note 1: You can’t instantiate generic, typesafe arrays in Java, for historical and technical reasons that are beyond the scope of the course. Note the workaround: array = (E[]) new Object[capacity]; works, but generates a warning. As long as no references to the array escape the enclosing object (that is, as long as we never return the E[] array to a caller) we’ll be OK.

Note 2: When we pop, we explicitly set the array cell to null. Why? (on board) we are removing the reference (from the stack) to the object so that it can be garbage collected later. This is a minor potential memory leak but it’s worth plugging.

Linked-list based stack

Recall there are two basic ways to agglomerate data in Java: you can use arrays, or you can use references. For this second implementation, let’s consider using linked lists. Again, as we showed earlier in the semester, we’ll need a simple Node structure to link together into the list.

package stack;

// note this is a super-discount Node class; a "real" one would probably have at
// least a constructor (that took an `E data` argument)
public class Node<E> {
	public Node<E> next;
	public E data;
}

And since we’re only interested in the “top” of the stack, it’s pretty easy to do.

package stack;

public class UnboundedLinkedStack<E> implements Stack<E> {
	private Node<E> head;
	private int size;
	
	@Override
	public void push(E e) throws StackOverflowException {
		Node<E> node = new Node<>();
		node.data = e;
		node.next = head;
		head = node;
		size++;
	}

	@Override
	public E pop() throws StackUnderflowException {
		if (isEmpty()) {
			throw new StackUnderflowException();
		}
		E temp = head.data;
		head = head.next;
		size--;
		return temp;
	}

	@Override
	public E peek() throws StackUnderflowException {
		if (isEmpty()) {
			throw new StackUnderflowException();
		}
		return head.data;
	}

	@Override
	public boolean isEmpty() {
		return head == null;
	}

	@Override
	public boolean isFull() {
		return false;
	}

	@Override
	public int size() {		
		return size;
	}

}