SEN2211 · Week 6 · Stacks for Algebraic Operations

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.

balanced parenthesesinfixpostfix (Reverse Polish Notation)prefixoperator precedenceassociativityoperandoperator stackpostfix evaluationShunting-yard

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:

  1. The last parenthesis opened must be the first closed. A stack checks exactly this.
  2. In postfix the order of operands doesn’t change. Only the operators move.
  3. A higher-precedence operator is popped from the stack first.
  4. 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

NotationExampleExplanation
infixA + B * Coperator in the middle, human language; needs parentheses and precedence
postfixA B C * +operator after the operands; no parentheses needed
prefix+ A * B Coperator 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
OperatorPrecedenceAssociativity
^3right to left
* / %2left to right
+ -1left 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 ^. ABCA^{B^C} means A(BC)A^{(B^C)} in mathematics. If you pop the ^ on top when an equal-precedence ^ arrives, you write AB^C^, which is (AB)C(A^B)^C. The right answer is ABC^^.

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

WrongCorrect
Only counting for parenthesesA stack for type and order; it must be empty at the end
Changing the operand order in postfixThe order A B C is preserved, only the operators move
Writing ( to the output when ) arrivesParentheses 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 ^ tooRight-associative: ABC^^
Forgetting to empty the stack at the endThe remaining operators go to the output from the top, in order
pop() - pop() while evaluatingright = pop(); left = pop(); left - right
Not checking for ( before an operatorIf the top is (, stop; ( leaves only with )

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

Using the stack algorithm, which of these strings are balanced? (a) {[()()]} (b) ([)] (c) (() (d) {[]}()

Show answer
(a) and (d)

(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.

Question 2medium

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
{( ; '}' pops '(' which does not match

{ → {, [ → {[, ( → {[(, ) → {[, ] → {, ( → {(. The last character } does not match the ( on top, so it is unbalanced.

Question 3easy

Convert to postfix: A + B * C

Show answer
A B C * +

* 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*+.

Question 4medium

Convert to postfix: (A + B) * C - D

Show answer
A B + C * D -

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-.

Question 5medium

Convert to postfix: A * (B + C) / D - E

Show answer
A B C + * D / E -

* 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: * ( +.

Question 6hard

Convert to postfix: (a) A - B - C (b) A ^ B ^ C, where ^ is right-associative.

Show answer
(a) A B - C - (b) A B C ^ ^

(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.

Question 7easy

Evaluate the postfix expression: 5 3 + 2 *

Show answer
16

5 and 3 are pushed. +: 3 and 5 are popped, 8 is pushed. 2 is pushed. *: 8 · 2 = 16.

Question 8medium

Evaluate the postfix expression and show the stack after each token: 6 2 3 + - 3 8 2 / + *

Show answer
7

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
*    7
The infix equivalent is (6 − (2 + 3)) · (3 + 8 / 2) = 1 · 7 = 7.

Question 9medium

Evaluate: 8 2 / 3 - 4 2 * +. Careful with the operand order for - and /.

Show answer
9

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.

Question 10medium

Write static boolean isBalanced(String s) that checks (), [] and {} using a stack. Other characters are ignored.

Show answer
Push openers; on a closer, fail if the stack is empty or the popped opener doesn't match; at the end the stack must be empty.

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.

Question 11hard

Write static int evalPostfix(String expr) for space-separated integer tokens and the operators + - * /.

Show answer
Push numbers; on an operator pop right then left, push left op right; return the single remaining value.

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.

Question 12hard

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
A B C - * D +; after ')': * ; after '+': +

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).

Review card

PARENTHESES: push the opener; on a closer, if the stack is empty or the top doesn't match → FALSE; the stack must be empty at the end INFIX A+B POSTFIX AB+ PREFIX +AB no parentheses in postfix, the order of operands doesn't change Precedence: ^ (3) > * / % (2) > + - (1) INFIX → POSTFIX: operand → straight to the output ( → push ) → pop until ( → output, discard ( operator → as long as the top is not (, if the top has HIGHER or EQUAL (left-associative) precedence → pop → output; then push at the end: pop what is left in the stack → output ^ is right-associative: DON'T pop on equal precedence (A^B^C → ABC^^) A-B-C → AB-C- POSTFIX EVALUATION: number → push; operator → right = pop, left = pop, push(left op right) At the end exactly ONE value must remain in the stack Check: convert the postfix back to infix and compare the result