Expression handling with a stack: parentheses, infix → postfix, postfix evaluation
Checking parenthesis balance, converting an infix expression to postfix with precedence rules, and evaluating a postfix expression with a stack. The exam asks you to draw a step-by-step stack table.
If you type 2 + 3 * 4 on a calculator you get 14, not 20. How does the machine apply the precedence rules? The answer: two stack algorithms. First it converts the expression to a parenthesis-free form (postfix), then it evaluates that form in a single left-to-right pass. The same stack idea is used for parenthesis matching in editors. The exam has three question types: is it balanced, convert to postfix (often with a stack table), evaluate the postfix.
Golden rules:
- The last parenthesis opened must be the first closed. A stack checks exactly this.
- In postfix the order of operands doesn’t change. Only the operators move.
- A higher-precedence operator is popped from the stack first.
- When evaluating postfix, the first one popped is the right operand.
Balanced parentheses
The algorithm: read from left to right. Push an opening parenthesis. When a closer arrives, if the stack is empty or the opener on top is not of the same type, it is unbalanced. If the stack is not empty at the end, it is unbalanced.
static boolean isBalanced(String s) {
Deque<Character> st = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') st.push(c);
else if (c == ')' || c == ']' || c == '}') {
if (st.isEmpty()) return false;
char open = st.pop();
if ((c == ')' && open != '(') || (c == ']' && open != '[') || (c == '}' && open != '{'))
return false;
}
}
return st.isEmpty();
}
{[()()]} -> true
([)] -> false
(() -> false
())( -> false
{[]}() -> true
a*(b+c] -> false
The state of the stack after each character (left to right: bottom → top):
input: ( [ ] { } )
( stack(bottom->top): (
[ stack(bottom->top): ([
] stack(bottom->top): (
{ stack(bottom->top): ({
} stack(bottom->top): (
) stack(bottom->top): (empty)
end: empty -> balanced
PITFALL · Counting is not enough. The check “number of openers = number of closers” says yes for
([)], but the expression is wrong. For)(the counts are equal too but the order is wrong. For type and order, a stack is a must.
Three notations: infix, postfix, prefix
| Notation | Example | Explanation |
|---|---|---|
| infix | A + B * C | operator in the middle, human language; needs parentheses and precedence |
| postfix | A B C * + | operator after the operands; no parentheses needed |
| prefix | + A * B C | operator before the operands |
Postfix is also called Reverse Polish Notation (RPN). Its advantage: the precedence information is built into the order, and the machine reads from left to right and evaluates with a single stack.
A quick way to convert by hand: fully parenthesize the expression, then move each operator to the place of its own closing parenthesis. A + B * C → (A + (B * C)) → A B C * +.
The infix → postfix algorithm
An output string and an operator stack are used.
For each token:
operand (letter/number) → write to the output
( → push onto the stack
) → pop and write to the output until ( is reached; discard (
operator op → as long as the stack is not empty, the top is not ( AND
[ prec(top) > prec(op) or prec(top) == prec(op) and op is left-associative ]
pop and write to the output; then push op
At the end: pop what is left in the stack and write it to the output
| Operator | Precedence | Associativity |
|---|---|---|
^ | 3 | right to left |
* / % | 2 | left to right |
+ - | 1 | left to right |
Java code (for single-letter operands):
static int prec(char op) {
switch (op) {
case '+': case '-': return 1;
case '*': case '/': case '%': return 2;
case '^': return 3;
}
return 0;
}
static String toPostfix(String infix) {
StringBuilder out = new StringBuilder();
Deque<Character> st = new ArrayDeque<>();
for (char c : infix.toCharArray()) {
if (c == ' ') continue;
if (Character.isLetterOrDigit(c)) out.append(c);
else if (c == '(') st.push(c);
else if (c == ')') {
while (st.peek() != '(') out.append(st.pop());
st.pop(); // discard (
} else {
while (!st.isEmpty() && st.peek() != '(' &&
(prec(st.peek()) > prec(c) || (prec(st.peek()) == prec(c) && c != '^')))
out.append(st.pop());
st.push(c);
}
}
while (!st.isEmpty()) out.append(st.pop());
return out.toString();
}
Worked trace: A + B * C - D / E
token stack output
A (empty) A
+ + A
B + AB
* +* AB
C +* ABC
- - ABC*+
D - ABC*+D
/ -/ ABC*+D
E -/ ABC*+DE
end (empty) ABC*+DE/-
The critical moment is the - token: there is a * on top (precedence 2 > 1), so it is popped. Then there is a + on top (equal precedence, left to right), which is popped too. Only after that is - pushed. When / arrives, the - on top has lower precedence, so it stays.
The program converted a series of expressions:
A+B*C -> ABC*+ maxStack=2
(A+B)*C-D -> AB+C*D- maxStack=2
A*(B+C)/D-E -> ABC+*D/E- maxStack=3
A-B-C -> AB-C- maxStack=1
A^B^C -> ABC^^ maxStack=2
(A+B)*(C-D) -> AB+CD-* maxStack=3
A+B*(C^D-E)^(F+G*H)-I -> ABCD^E-FGH*+^*+I- maxStack=6
maxStack is the largest size the stack reached during the conversion; it is sometimes asked on the exam.
PITFALL · The right-associative
^. means in mathematics. If you pop the^on top when an equal-precedence^arrives, you writeAB^C^, which is . The right answer isABC^^.
Evaluating a postfix expression
This time the stack holds numbers.
For each token:
number → push
operator → right = pop(), left = pop(), push(left op right)
At the end a single number remains in the stack: the result
static int evalPostfix(String expr) {
Deque<Integer> st = new ArrayDeque<>();
for (String tok : expr.trim().split("\\s+")) {
if (tok.matches("-?\\d+")) st.push(Integer.parseInt(tok));
else {
int right = st.pop();
int left = st.pop();
switch (tok) {
case "+": st.push(left + right); break;
case "-": st.push(left - right); break;
case "*": st.push(left * right); break;
case "/": st.push(left / right); break;
}
}
}
return st.pop();
}
Worked trace: 4 7 2 - * 3 +
The stack after each token (bottom → top):
4 4
7 4 7
2 4 7 2
- 4 5
* 20
3 20 3
+ 23
23
Check: convert the postfix back to infix. 7 2 - = (7 − 2), 4 (7−2) * = 4 · 5, 20 3 + = 23. Correct.
TIP · Operand order. For
9 5 - 2 -the correct result is (9 − 5) − 2 = 2. If you take the pop order the other way, you get first 5 − 9 = −4, then 2 − (−4) = 6. Memorize the sentence “the first one out goes on the right”.
Detecting a faulty expression: if when an operator arrives there aren’t two numbers in the stack, or if more than one number remains at the end, the expression is broken (like 3 +).
Common mistakes
| Wrong | Correct |
|---|---|
| Only counting for parentheses | A stack for type and order; it must be empty at the end |
| Changing the operand order in postfix | The order A B C is preserved, only the operators move |
Writing ( to the output when ) arrives | Parentheses never enter the output |
Leaving an equal-precedence operator in the stack (A-B-C → ABC--) | If left-associative, pop it: AB-C- |
Popping on equal precedence for ^ too | Right-associative: ABC^^ |
| Forgetting to empty the stack at the end | The remaining operators go to the output from the top, in order |
pop() - pop() while evaluating | right = pop(); left = pop(); left - right |
Not checking for ( before an operator | If the top is (, stop; ( leaves only with ) |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
Using the stack algorithm, which of these strings are balanced? (a) {[()()]} (b) ([)] (c) (() (d) {[]}()
Show answer
(a) every closer matches the opener on top, and the stack is empty at the end. (b) when ) arrives, the top has [: mismatch. (c) A ( is left in the stack at the end. (d) two separate balanced groups, empty at the end.
The string { [ ( ) ] ( } is checked with a stack. What does the stack contain (bottom → top) just before the last character is read, and why does the check fail?
Show answer
{ → {, [ → {[, ( → {[(, ) → {[, ] → {, ( → {(. The last character } does not match the ( on top, so it is unbalanced.
Convert to postfix: A + B * C
Show answer
* has higher precedence than +. + goes onto the stack, and the * after B does not pop it, it goes on top. At the end the stack is emptied from the top: first *, then +. The output is ABC*+.
Convert to postfix: (A + B) * C - D
Show answer
The inside of the parentheses first: AB+. Then * goes in and C is written. When - arrives, the * on top has higher precedence and is popped. Then D, and finally -. The output is AB+C*D-.
Convert to postfix: A * (B + C) / D - E
Show answer
* goes in. ( goes in, B, +, C. When ) arrives, + is popped: ABC+. When / arrives, the * on top has the same precedence and is left-to-right, so it is popped: ABC+*. D is written. When - arrives, / is popped. E, and finally -. The moment the stack holds the most, 3 elements: * ( +.
Convert to postfix: (a) A - B - C (b) A ^ B ^ C, where ^ is right-associative.
Show answer
(a) - is left to right: (A − B) − C. When the second - arrives, the - on top with equal precedence is popped: AB-C-. (b) ^ is right to left: A ^ (B ^ C). The ^ with equal precedence is not popped, the new one is pushed on top; at the end both are popped: ABC^^. Rule: with equal precedence, pop if it is left-associative, don't pop if it is right-associative.
Evaluate the postfix expression: 5 3 + 2 *
Show answer
5 and 3 are pushed. +: 3 and 5 are popped, 8 is pushed. 2 is pushed. *: 8 · 2 = 16.
Evaluate the postfix expression and show the stack after each token: 6 2 3 + - 3 8 2 / + *
Show answer
6 6
2 6 2
3 6 2 3
+ 6 5
- 1 (6 - 5)
3 1 3
8 1 3 8
2 1 3 8 2
/ 1 3 4 (8 / 2)
+ 1 7
* 7The infix equivalent is (6 − (2 + 3)) · (3 + 8 / 2) = 1 · 7 = 7.Evaluate: 8 2 / 3 - 4 2 * +. Careful with the operand order for - and /.
Show answer
8 2 / → 8 / 2 = 4. 4 3 - → 4 − 3 = 1. 4 2 * → 8. 1 8 + → 9. At an operator the first one popped is the right operand: right = pop(); left = pop(); left - right. If you do it the other way you get wrong results like 2 / 8 = 0 and 3 − 4 = −1.
Write static boolean isBalanced(String s) that checks (), [] and {} using a stack. Other characters are ignored.
Show answer
static boolean isBalanced(String s) {
Deque<Character> st = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (st.isEmpty()) return false; // extra closer
char open = st.pop();
if ((c == ')' && open != '(') ||
(c == ']' && open != '[') ||
(c == '}' && open != '{')) return false; // type mismatch
}
}
return st.isEmpty(); // is any opener left open
}Three kinds of error: ())( (a closer when empty), ([)] (type mismatch), (() (non-empty at the end). a*(b+c] → false.Write static int evalPostfix(String expr) for space-separated integer tokens and the operators + - * /.
Show answer
static int evalPostfix(String expr) {
Deque<Integer> st = new ArrayDeque<>();
for (String tok : expr.trim().split("\\s+")) {
if (tok.matches("-?\\d+")) {
st.push(Integer.parseInt(tok));
} else {
int right = st.pop(); // RIGHT first
int left = st.pop();
switch (tok) {
case "+": st.push(left + right); break;
case "-": st.push(left - right); break;
case "*": st.push(left * right); break;
case "/": st.push(left / right); break;
}
}
}
return st.pop();
}4 7 2 - * 3 + → 4 · (7 − 2) + 3 = 23. In a faulty expression (3 +) the second pop throws a NoSuchElementException on the empty stack.Convert A * (B - C) + D to postfix and give the operator stack (bottom → top) right after the token ) is processed and right after + is processed.
Show answer
token stack output
A (empty) A
* * A
( *( A
B *( AB
- *(- AB
C *(- ABC
) * ABC-
+ + ABC-*
D + ABC-*D
end (empty) ABC-*D+When ) arrives, everything up to ( is popped (-), and ( is discarded. When + arrives, the * on top has higher precedence and is popped; then + is pushed.Verification: The parenthesis checker, the infix → postfix converter and the postfix evaluator were written in verify/SEN2211/W06.java and run on the JDK (java W06.java). The trace tables on the page are taken verbatim from the stack and output lines the program prints after each token; the postfix result was compared with the direct evaluation of the same infix expression in Java (7).