Stack: with an array and with a linked list
LIFO logic, push/pop/peek, two implementations with an array and a linked list, overflow and underflow. The exam asks for the state of the stack after a sequence of operations and for pop order questions.
A stack is like plates piled on top of each other: you put a plate on the top, and when taking one you take the one on top. You can’t pull a plate from the middle. This rule is called LIFO (Last In, First Out). It looks simple, but compilers’ expression evaluation, method calls, the undo feature and parenthesis checking all work with a stack. Exam questions are of two types: finding what is printed or what is left in the stack after a sequence of operations, and writing push/pop with an array or a linked list.
Golden rules:
- You only reach the top. push puts on the top, pop takes from the top.
- peek looks, it doesn’t remove. When tracing, don’t confuse pop with peek.
- In an array, top is the index of the last element. -1 in an empty stack.
- In a linked list the top is the head of the list. Not the end.
- pop from an empty stack = underflow, push to a full array = overflow.
Abstract data type (ADT)
A stack is an abstract data type: it says which operations exist, not how they are done. The operations:
| Operation | What it does | Time |
|---|---|---|
push(x) | puts x on the top | O(1) |
pop() | removes and returns the top element | O(1) |
peek() (or top()) | returns the top element, doesn’t remove it | O(1) |
isEmpty() | is the stack empty | O(1) |
size() | number of elements | O(1) |
We can implement the same ADT in two ways: with an array or with a linked list. The code that uses it sees no difference.
Stack with an array (array-based stack)
class ArrayStack {
private int[] data;
private int top = -1; // index of the last element
ArrayStack(int capacity) { data = new int[capacity]; }
boolean isEmpty() { return top == -1; }
boolean isFull() { return top == data.length - 1; }
int size() { return top + 1; }
void push(int x) {
if (isFull()) throw new RuntimeException("Stack overflow");
data[++top] = x; // increment first, then write
}
int pop() {
if (isEmpty()) throw new RuntimeException("Stack underflow");
return data[top--]; // read first, then decrement
}
int peek() {
if (isEmpty()) throw new RuntimeException("Stack underflow");
return data[top];
}
}
The difference between ++top and top-- is critical here. If you write data[top++] = x, the first element is written to data[-1] and you get an ArrayIndexOutOfBoundsException.
Worked example
ArrayStack s = new ArrayStack(5);
s.push(10); s.push(20); s.push(30);
System.out.println(s.pop());
System.out.println(s.peek());
s.push(40);
System.out.println(s.size() + " " + s.raw()); // raw: the raw state of the array
30
20
3 [10, 20, 40, 0, 0] top=2
push 10 [10, _, _, _, _] top=0
push 20 [10, 20, _, _, _] top=1
push 30 [10, 20, 30, _, _] top=2
pop → 30 [10, 20, 30, _, _] top=1 (30 is still in the array but not in the stack)
peek → 20 no change
push 40 [10, 20, 40, _, _] top=2 (written over the 30)
PITFALL · pop doesn’t erase from the array. If the exam asks “what are the contents of the array?”, the popped values are still there; they just sit above the top index. A new push writes over them.
Growing when full
Instead of a fixed capacity, when full you can open a new array of double the size and copy. That push takes O(n), but since the capacity doubles each time, this happens rarely. The total cost of n pushes is O(n), that is O(1) amortized per push. Java’s ArrayList and ArrayDeque classes work this way.
Stack with a linked list (linked stack)
Inserting at the head of a singly linked list and deleting from the head are already O(1). Top = head.
class LinkedStack {
private static class Node {
int data; Node next;
Node(int d, Node n) { data = d; next = n; }
}
private Node top;
boolean isEmpty() { return top == null; }
void push(int x) { top = new Node(x, top); }
int pop() {
if (top == null) throw new RuntimeException("Stack underflow");
int x = top.data;
top = top.next;
return x;
}
int peek() {
if (top == null) throw new RuntimeException("Stack underflow");
return top.data;
}
}
In this implementation there is no overflow (until memory runs out). The cost is one next reference per node.
| Array | Linked list | |
|---|---|---|
| push / pop / peek | O(1) | O(1) |
| Capacity | fixed (or amortized with growing) | unbounded |
| Overflow | yes | no |
| Extra memory | unused array cells | a next in every node |
Classic jobs done with a stack
Reversing. Push the letters in order, then pop: “DATA” → “ATAD”. The order that goes into the stack is reversed on the way out.
Decimal to binary. Push the remainders of n % 2, halve n; then pop everything. 13 → 1101, 6 → 110, 32 → 100000.
static String toBinary(int n) {
if (n == 0) return "0";
Deque<Integer> s = new ArrayDeque<>();
while (n > 0) { s.push(n % 2); n /= 2; }
StringBuilder sb = new StringBuilder();
while (!s.isEmpty()) sb.append(s.pop());
return sb.toString();
}
Method calls. On every method call, Java puts the local variables in a stack frame; when the method ends, the frame is popped. Infinite recursion fills this stack and gives a StackOverflowError (week 10).
Other applications (parenthesis balance, infix → postfix, postfix evaluation) are the subject of next week.
Pop order questions
“1, 2, 3, 4 are pushed in order, and a pop can happen at any moment in between. Which output order is possible?” To solve it, simulate: push up to the number that needs to come out, then check whether the top is that number.
Target 3 1 4 2:
3 needed → push 1, 2, 3 → pop 3 stack [1, 2]
1 needed → top is 2, not 1 IMPOSSIBLE
Target 3 2 4 1:
push 1,2,3 → pop 3 → [1,2]
pop 2 → [1]
push 4 → pop 4 → [1]
pop 1 POSSIBLE
Short rule: after a number is popped, the numbers smaller than it that have not yet come out must come out in decreasing order.
A ready-made stack in Java
The java.util.Stack class is old (derived from Vector, synchronized, slow). In modern Java the recommended one is the Deque interface and the ArrayDeque class:
Deque<Integer> st = new ArrayDeque<>();
st.push(1); st.push(2);
st.peek(); // 2
st.pop(); // 2
On an empty ArrayDeque, pop() throws NoSuchElementException, while peek() returns null. Details in week 9.
Common mistakes
| Wrong | Correct |
|---|---|
| peek removes the element | It only looks |
| Putting the top at the end of the list in a linked stack | Head = top, both are O(1) |
data[top++] = x (when top = -1) | data[++top] = x |
return data[--top] | return data[top--] |
| pop erases the value from the array | Only top decreases, the value stays |
| No pop check on an empty stack | if (isEmpty()) throw ... |
| Answering a pop order question by instinct | Simulate step by step |
isFull: top == data.length | top == data.length - 1 |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
What is printed? (s is an empty stack.)
s.push(5); s.push(3);
System.out.print(s.pop() + " ");
s.push(8); s.push(1);
System.out.print(s.pop() + " ");
System.out.print(s.pop() + " ");
s.push(6);
System.out.println(s.peek());Show answer
[5,3] → pop 3 → [5] → [5,8,1] → pop 1 → pop 8 → [5] → push 6 → [5,6], peek 6 (does not remove). The output: 3 1 8 6.
What is printed, and what is left in the stack (top first)?
for (int i = 1; i <= 5; i++) {
s.push(i * 2);
if (i % 2 == 0)
System.out.print(s.pop() + " ");
}Show answer
i=1: push 2. i=2: push 4, pop 4. i=3: push 6. i=4: push 8, pop 8. i=5: push 10. Printed: 4 8. What remains (from the top): 10 6 2.
An array stack has capacity 4, top starts at -1 and push does data[++top] = x. After the calls below, what are the contents of the array and the value of top?
push(7); push(2); push(9);
pop(); pop();
push(4);Show answer
After the pushes: [7,2,9,0], top 2. The two pops only decrease top: top 0. The values stay in the array but logically are not in the stack. push(4) writes to index 1 and lands on top of 2: [7,4,9,0], top 1. 9 is still in the array but not part of the stack.
An array stack with capacity 3 runs this loop. What is printed?
try {
for (int i = 1; i <= 4; i++) {
s.push(i);
System.out.print(i + " ");
}
} catch (RuntimeException e) {
System.out.println("-> " + e.getMessage());
}Show answer
Three pushes succeed, and the number is printed after each. At the fourth push the array is full (top == length - 1), push throws an exception, and it falls into the catch without printing 4.
Write int pop() for an array-based stack with fields int[] data and int top (top = index of the top element, -1 when empty). Throw an exception on underflow.
Show answer
int pop() {
if (top == -1)
throw new RuntimeException("Stack underflow");
return data[top--]; // read first, then decrement
}If you write data[--top] you return the element below. Since top-- is postfix, it is read with the old value of top first. O(1).Write void push(int x) and int pop() for a linked-list stack whose only field is Node top (Node has data and next, and a constructor Node(int d, Node n)).
Show answer
In a linked list the top of the stack is the head of the list; that way both operations are O(1).
void push(int x) {
top = new Node(x, top);
}
int pop() {
if (top == null)
throw new RuntimeException("Stack underflow");
int x = top.data;
top = top.next;
return x;
}If the top were at the end of the list, pop would need to search for the node before the last, which would be O(n).The numbers 1, 2, 3, 4 are pushed in this order; pops may happen at any time. Which of these output sequences are possible? (a) 2 4 3 1 (b) 3 1 4 2 (c) 4 3 2 1 (d) 1 4 2 3
Show answer
(a) push 1, push 2, pop 2, push 3, push 4, pop 4, pop 3, pop 1: possible. (c) push all four, pop in order: possible. (b) When 3 comes out, 1 and 2 are in the stack, with 2 on top. To pop 1, 2 must come out first: impossible. (d) When 4 comes out, 2 and 3 are inside, with 3 on top, so 2 can't come out first: impossible. Rule: after a number is popped, the smaller ones must come out in decreasing order.
What are the time complexities of push, pop and peek for an array stack and for a linked stack?
Show answer
In the array only the top index changes, in the list only the head node changes. None depends on n. In an implementation that copies to a new array of double the size when the array is full, that one push is O(n), but on average (amortized) it is still O(1).
What does this method return for "DATA"?
static String f(String w) {
Deque<Character> st = new ArrayDeque<>();
for (char ch : w.toCharArray()) st.push(ch);
StringBuilder sb = new StringBuilder();
while (!st.isEmpty()) sb.append(st.pop());
return sb.toString();
}Show answer
The letters enter in the order D, A, T, A; the top is the last A. When popped, A, T, A, D come out. The stack reverses the order: ATAD.
The values 3 8 6 1 4 are pushed onto s in this order. What is printed?
while (!s.isEmpty()) {
int x = s.pop();
if (x % 2 == 0) t.push(x);
}
while (!t.isEmpty())
System.out.print(t.pop() + " ");Show answer
The pop order from s is 4 1 6 8 3. The evens enter t in the order 4, 6, 8, with 8 on top. The pop order from t is 8 6 4. Because it was reversed twice, the evens returned to the original insertion order.
Write static String toBinary(int n) that converts a positive integer to its binary string using a stack.
Show answer
The remainders start from the lowest digit, but when writing we start from the highest. The stack reverses the order.
static String toBinary(int n) {
if (n == 0) return "0";
Deque<Integer> s = new ArrayDeque<>();
while (n > 0) {
s.push(n % 2);
n /= 2;
}
StringBuilder sb = new StringBuilder();
while (!s.isEmpty()) sb.append(s.pop());
return sb.toString();
}13 → remainders 1, 0, 1, 1 → output 1101. 6 → 110, 32 → 100000.Stack s contains (bottom to top) 4 7 2. What is printed?
int sum = 0;
while (s.peek() != 4)
sum += s.pop();
System.out.println(sum + " " + s.peek());Show answer
Top is 2: not 4, pop, sum = 2. Top is 7: pop, sum = 9. Top is 4: the loop ends. 4 stays in the stack, and peek returns 4. The output: 9 4. If there were no 4 at all, peek would throw underflow when the stack became empty.
Verification: ArrayStack and LinkedStack were written in verify/SEN2211/W05.java and run on the JDK (java W05.java). Whether pop orders are possible was checked in the same file with a simulation that pushes 1..n in order.