Fundamental data types
Stack: examine the item most recently added (LIFO)
Queue: examine the item least recently added (FIFO)

Separate interface and implementation
For example: stack, queue, bag, priority queue, symbol table, union-find, ...
Benefits:
Warmup API: stack of strings data type
![]() |
![]() |
Warmup client: reverse sequence of strings from standard input
A. Can't be done efficiently with a singly-linked list.
B.

C.

first to first node in a singly-linked listfirstfirst
// inner class
private class Node {
String item;
Node next;
}
public String pop() {
String item = first.item; // save item to return
first = first.next; // delete first node
return item; // return saved item
}

public void push(String item) {
Node oldfirst = first; // save a link to the list
first = new Node(); // create a new node for the beginning
first.item = item; // set the instance variables in
first.next = oldfirst; // the new node
}

public class LinkedStackOfStrings {
private Node first = null;
private class Node { // private inner class
String item; // (access modifiers for instance
Node next; // variables don't matter)
}
public boolean isEmpty() {
return first == null;
}
public void push(String item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public String pop() {
String item = first.item;
first = first.next;
return item;
}
}
Proposition: Every operation takes constant time in the worst case
Proposition: A stack with \(N\) items uses \(\texttilde 40 N\) bytes.
private class Node {
String item;
Node next;
}
|
![]() |
Remark: This accounts for the memory for the stack, but not the memory for Strings themselves, which the client owns.
A. Can't be done efficiently with an array?
B.

C.

s[] to store N items on stackpush(): add new item at s[N]pop(): remove item from s[N-1]
Defect: Stack overflows when N exceeds capacity! (stay tuned...)
public class FixedCapacityStackOfStrings {
private String[] s;
private int N = 0;
public FixedCapacityStackOfStrings(int capacity) {
s = new String[capacity]; // a cheat (stay tuned!)
}
public boolean isEmpty() {
return N == 0;
}
public void push(String item) {
s[N++] = item; // use to index into array, then increment N
}
public String pop() {
return s[--N]; // decrement N, then use to index into array
}
}
Overflow and underflow
Null items: we allow null items to be inserted
Loitering: holding a reference to an object when it is no longer needed
Following code allows for loitering.
public String pop() {
return s[--N];
}
Following code avoids loitering, so garbage collector can reclaim memory for an object only if no outstanding references.
public String pop() {
String item = s[--N];
s[N] = null;
return item;
}
Problem: requiring client to provide capacity does not implement API!
Q: How to grow and shrink array?
First try:
push(): increase size of array s[] by 1.pop(): decrease size of array s[] by 1.Too expensive!
Resizing by increasing size of array by 1:
Challenge: Ensure that array resizing happens infrequently
Q. How to grow array?
A. If array is full, create a new array of twice the size, and copy items. (twice = repeated doubling)
public ResizingArrayStackOfStrings() {
s = new String[1];
}
public void push(String item) {
if(N == s.length) resize(2 * s.length);
s[N++] = item;
}
private void resize(int capacity) {
String[] copy = new String[capacity];
for(int i = 0; i < N; i++) copy[i] = s[i];
s = copy;
}
Array accesses to insert first \(N = 2^i\) items: \(\underbrace{N}_1 + \underbrace{(2+4+8+\ldots+N)}_k \texttilde 3N\)
Q: How to shrink array?
First try:
push(): double size of s[] when array is fullpop(): halve size of array s[] when array is one-half fullToo expensive in worst case




Q. How to shrink array?
Efficient solution:
push(): double size of array s[] when array is fullpop(): halve size of array s[] when array is one-quarter fullpublic String pop() {
String item = s[--N];
s[N] = null;
if(N > 0 && N == s.length/4) resize(s.length/2);
return item;
}
Invariant: Array is between 25% and 100% full
Proposition: starting from an empty stack, any sequence of \(M\) push and pop operations takes time proportional to \(M\)
Order of growth of running time for resizing stack with \(N\) items:
| operation | best | worst | amortized |
|---|---|---|---|
construct |
\(1\) | \(1\) | \(1\) |
push |
\(1\) | \(N\) | \(1\) |
pop |
\(1\) | \(N\) | \(1\) |
size |
\(1\) | \(1\) | \(1\) |
\(N\) for push and pop because of doubling and halving operations.
Proposition: uses between \(\texttilde 8N\) and \(\texttilde 32N\) bytes to represent a stack with \(N\) items
public class ResizingArrayStackOfStrings {
private String[] s; // 8 bytes * array size
private int N = 0;
// ...
}
Remark: this accounts for the memory for the stack, but not the memory for strings themselves, which the client owns.
Tradeoffs: can implement a stack with either resizing array or linked list; client can use interchangeably. Which one is better?
Linked-list implementation
Resizing-array implementation



A. Can't be done efficiently with a singly-linked list?
B.

C.

first to first node in singly-linked listlast to last nodefirstlast
public String dequeue() {
String item = first.item; // save item to return
first = first.next; // delete first node
return item; // return saved item
}

Remark: Identical code to linked-list stack pop
public String enqueue(String item) {
Node oldlast = last; // save a link to the last node
last = new Node(); // create a new node for end
last.item = item;
oldlast.next = last; // link new node to end of list
}

public class LinkedQueueOfStrings {
private Node first, last;
private class Node { /* same as in LinkedStackOfStrings */ }
public boolean isEmpty() { return first == null; }
public void enqueue(String item) {
Node oldlast = last;
last = new Node();
last.item = item;
last.next = null;
if(isEmpty()) first = last; // handle special case
else oldlast.next = last; // for empty queue
}
public String dequeue() {
String item = first.time
first = first.next;
if(isEmpty()) last = null; // handle special case...
return item;
}
}
A. Can't be done efficiently with an array.
B.

C.

A. Can't be done efficiently with an array.
B.

C.

D. Either B or C
q[] to store items in queueenqueue adds new item at q[tail]dequeue removes item from q[head]head and tail modulo the capacity
Q: How to resize?
We implemented StackOfStrings
We also want StackOfURLs, StackOfInts, StackOfVans, ...
Attempt 1: Implement a separate stack class for each type
Unfortunately, this was most reasonable approach in Java until 1.5!
We implemented StackOfStrings
We also want StackOfURLs, StackOfInts, StackOfVans, ...
Attempt 2: Implement a stack with items of type Object
StackOfObjects s = new StackOfObjects(); Apple a = new Apple(); Banana b = new Banana(); s.push(a); s.push(b); a = (Apple) (s.pop()); // run-time error!
We implemented StackOfStrings
We also want StackOfURLs, StackOfInts, StackOfVans, ...
Attempt 3: Java generics
Stack<Apple> s = new Stack<Apple>(); // ^^^^^ ^^^^^ type parameter Apple a = new Apple(); Banana b = new Banana(); s.push(a); s.push(b); // compile-time error a = s.pop();
Guiding principles: welcome compile-time errors; avoid run-time errors
public class LinkedStackOfStrings {
private Node first = null;
private class Node { // private inner class
String item; // (access modifiers for instance
Node next; // variables don't matter)
}
public boolean isEmpty() { return first == null; }
public void push(String item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public String pop() {
String item = first.item;
first = first.next;
return item;
}
}
public class LinkedStack<Item> {
private Node first = null;
private class Node { // private inner class
Item item; // (access modifiers for instance
Node next; // variables don't matter)
}
public boolean isEmpty() { return first == null; }
public void push(Item item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public Item pop() {
Item item = first.item;
first = first.next;
return item;
}
}
public class FixedCapacityStackOfStrings {
private String[] s;
private int N = 0;
public FixedCapacityStackOfStrings(int capacity) {
s = new String[capacity];
}
public boolean isEmpty() { return N == 0; }
public void push(String item) { s[N++] = item; }
public String pop() { return s[--N]; }
}
public class FixedCapacityStack<Item> {
private Item[] s;
private int N = 0;
public FixedCapacityStackOfStrings(int capacity) {
s = new Item[capacity]; // <- not allowed in Java :(
}
public boolean isEmpty() { return N == 0; }
public void push(Item item) { s[N++] = item; }
public Item pop() { return s[--N]; }
}
Except line 6 (generic array creation) is not allowed in Java :(

public class FixedCapacityStack<Item> {
private Item[] s;
private int N = 0;
public FixedCapacityStackOfStrings(int capacity) {
s = (Item[]) new Object[capacity]; // ugly cast :(
}
public boolean isEmpty() { return N == 0; }
public void push(Item item) { s[N++] = item; }
public Item pop() { return s[--N]; }
}
A (ugly) cast will "fix" the problem :|

May get a compiler warning
$ javac GenericArrays.java
Note: GenericArrays.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$ javac -Xlint:unchecked GenericArrays.java
GenericArrays.java:6: warning: [unchecked] unchecked cast
items = (Item[]) new Object[5];
^
required: Item[]
found: Object[]
where Item is a type-variable:
Item extends Object declared in class GenericArrays
1 warning
Q: Why does Java make me cast (or use reflection)?
Short answer: Backward compatibility.
Long answer: Need to learn about type erasure and covariant arrays.
Q: What to do about primitive types? (int, double, boolean, etc.)
Wrapper type
Integer for int, Double for double, Boolean for boolean, etc.Stack<Integer> s = new Stack<Integer>(); s.push(17); // s.push(Integer.valueOf(17)); int a = s.pop(); // int a = s.pop().intValue();
Bottom line: Client code can use generic stack for any type of data
Design challenge: Support iteration over stack items by client, without revealing the internal representation of the stack.


Java solution: Make stack implement the java.lang.Iterable interface
Q. What is an Iterable?
A. Has a method that returns an Iterator.
Q. What is an Iterator?
A. Has methods hasNext() and next().
// java.lang.Iterable interface
public interface Iterable<Item> {
Iterator<Item> iterator();
}
// java.util.Iterator interface
public interface Iterator<Item> {
boolean hasNext();
Item Next();
void remove(); // optional; use at your own risk
}
Q. Why make data structures Iterable?
A. Java support elegant client code.
// "foreach" statement (shorthand)
for(String s : stack) {
StdOut.println(s);
}
// equivalent code (longhand)
Iterator<String> i = stack.iterator();
while(i.hasNext()) {
String s = i.next();
StdOut.println(s);
}
Version 1: named Iterator class
import java.util.Iterator; // <- must import this!
public class Stack<Item> implements Iterable<Item> {
/* ... */
public Iterator<Item> iterator() { return new ListIterator(); }
private class ListIterator implements Iterator<Item> {
private Node<Item> current = first;
public boolean hasNext() { return current != null; }
public void remove() { /* not supported */ }
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
}
}

Version 2: unnamed Iterator class
import java.util.Iterator; // <- must import this!
public class Stack<Item> implements Iterable<Item> {
/* ... */
public Iterator<Item> iterator() {
return new Iterator<Item>() {
private Node<Item> current = first;
public boolean hasNext() { return current != null; }
public void remove() { /* not supported */ }
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
};
}
}

Version 1: named Iterator class
public class Stack<Item> implements Iterable<Item> {
/* ... */
public Iterator<Item> iterator() {
return new ReverseArrayIterator();
}
private class ReverseArrayIterator implements Iterator<Item> {
private int i = N;
public boolean hasNext() { return i > 0; }
public void remove() { /* not supported */ }
public Item next() { return s[--i]; }
}
}

Q. What if client modifies the data structure while iterating?
A. A fail-fast iterator throws a java.util.ConcurrentModificationException.
// concurrent modification :( for(String s : stack) stack.push(s);
Q. How to detect?
A. Simple!
push() and pop() operations in StackIterator subclass upon creationnext() and hasNext(), the current count and saved count do not equal (client called push or pop), throw exceptionList interface: java.util.List is API for a sequence of items

Implementations: java.util.ArrayList uses resizing array; java.util.LinkedList uses linked list.
Caveat: only some operations are efficient!
java.util.Stack
push(), pop(), and iterationjava.util.Vector, which implements java.util.List interface from previous slide, including get() and remove()Java 1.3 bug report (June 27, 2001)
“The iterator method on java.util.Stack iterates through a Stack from the bottom up. One would think that it should iterate as if it were popping off the top of the Stack.
”
Status (closed, will not fix)
“It was an incorrect design decision to have Stack extend Vector ("is-a" rather than "has-a"). We sympathize with the submitter but cannot fix this because of compatibility.
”
java.util.Stack
java.util.Queue: An interface, not an implementation of a queue
Best practices: use provided implementations of Stack, Queue, and Bag
Generate random tile locations in \(N\)-by-\(N\) HexBoard
row,col at random; if already set, repeatjava.util.ArrayList of \(N^2\) unset tile locations“Why is my program so slow?
”
Lesson: Don't use a library until you understand its API!
This course: Can't use a library until we've implemented it in class
How a compiler implements a function
Recursive function: Function that calls itself
Note: Can always use an explicit stack to remove recursion
Recursion example: greatest common divisor
static int gcd(int p, int q) {
if(q == 0) return p;
return gcd(q, p % q);
}
gcd(216, 192) // stack: p=216, q=192
gcd(192, 24) // stack: 216, 192, p=192, q=24
gcd(24, 0) // stack: 216, 192, 192, 24, p=24, q=0
<- 24 // stack: 216, 192, 192, 24, ret=24
<- 24 // stack: 216, 192, ret=24
<- 24 // stack: ret=24
Goal: evaluate infix expressions
( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
^ operand ^ operator
Two-stack algorithm (E. W. Dijkstra)
Context: an interpreter!

infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)


infix expression (fully parenthesized)

public class Evaluate {
public static void main(String[] args) {
Stack<String> ops = new Stack<String>();
Stack<Double> vals = new Stack<Double>();
while(!StdIn.isEmpty()) {
String s = StdIn.readString();
if (s.equals("(")) ;
else if(s.equals("+")) ops.push(s);
else if(s.equals("*")) ops.push(s);
else if(s.equals(")")) {
String op = ops.pop();
if (op.equals("+"))
vals.push(vals.pop() + vals.pop());
else if(op.equals("*"))
vals.push(vals.pop() * vals.pop());
} else vals.push(Double.parseDouble(s));
}
StdOut.println(vals.pop());
}
}
$ java Evaluate ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) ) 101.0
Q. Why correct?
A. When algorithm encounters an operator surrounded by two values within parentheses, it leaves the result on the value stack as if it was the original input.
input: ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
processing: ( 1 + ( ( 2 + 3 ) ...
( 1 + ( 5 * ( 4 * 5 ) ...
( 1 + ( 5 * 20 ) ...
( 1 + 100 )
output: 101
Extensions: More ops, precedence order, associativity
Observation 1: Dijkstra's two-stack algorithm computes the same value if the operator occurs after the two values
Observation 2: All the parentheses are redundant!
infix: ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) ) postfix: ( 1 ( ( 2 3 + ) ( 4 5 * ) * ) + ) no ()s: 1 2 3 + 4 5 * * + short: 1 2 3 + 4 5 * * +
Bottom line: Postfix or "reverse Polish" notation
Applications: Postscript, Forth, calculators, Java virtual machine, ...

