CS 136 - Lecture 16

  1. Linear Structures
    1. Stacks
    2. Stack Implementations
      1. Array-based implementation
      2. Linked list implementation
      3. Analyzing the implementations
    3. Applications of stacks
      1. Running mazes
      2. Evaluation of arithmetic expressions
      3. Run-time stacks on computers

Linear Structures

public interface Linear extends Store
{  // get size, isEmpty, & clear from Store.

    public void add(Object value);
    // pre: value is non-null
    // post: the value is added to the collection,
    //       the replacement policy not specified.

    public Object remove();
    // pre: structure is not empty.
    // post: removes an object from container

    public Object peek();
    // pre: structure is not empty.
    // post: returns ref to next object to be removed.
}
Look at two particular highly restricted linear structures:

Stacks

Stacks can be described recursively: Here is the picture of a stack of integers:

public interface Stack extends Linear {
    public void push(Object item);
    // post: item is added to stack
    //       will be popped next if no further push

    public Object pop();
    // pre: stack is not empty
    // post: most recently pushed item is removed & returned

    public void add(Object item);
    // post: item is added to stack
    //       will be popped next if no further add

    public Object remove();
    // pre: stack is not empty
    // post: most recently added item is removed and returned

    public Object peek();
    // pre: stack is not empty
    // post: top value (next to be popped) is returned

    public boolean empty();
    // post: returns true if and only if the stack is empty


Stack Implementations

As we saw earlier, stacks are essentially restricted lists and therefore have similar representations.

Array-based implementation

Since all operations are at the top of the stack, the array implementation is now much, much better.
public class StackArray implements Stack
{
    protected int top;
    protected Object[] data;
    ...
The array implementation keeps the bottom of the stack at the beginning of the array. It grows toward the end of the array.

The only problem is if you attempt to push an element when the array is full. If so

    Assert.pre(!isFull(),"Stack is not full.");
will fail, raising an exception.

Thus it makes more sense to implement with Vector (see StackVector) to allow unbounded growth (at cost of occasional O(n) delays).

Complexity:


Linked list implementation

The linked list implementation is singly-linked with references pointing from the top to the bottom of the stack.

Analyzing the implementations:

Operations:

Applications of stacks

Running mazes


  public void runMaze() 
  {
    success = false;                 // No solution yet
    
    current = start;                 // Initialize current to start
    current.visit();
    
    path = new StackList();          // Create new path
    path.push(current);              // with start on it
    
    while (!path.empty() && !success)   
      {                              // Search until success or run out of cells
         current = nextUnvisited();  // Get new cell to visit
                
         if (current != null) {      // If unvisited cell, 
            current.visit();         // move to it &
            path.push(current);      // push it onto path
            success = current.equals(finish); // Done yet?
          } 
          else        // No neighbors to visit so back up
          {           // Pop off last cell visited
             current = (Position)path.pop();   
             if (!path.empty())      // If path non-empty take last 
                                     //  visited as new current
                current = (Position)path.peek();
          }
      }
  }


Evaluation of arithmetic expressions

Some machines have stack based machine language instructions.

Example. PUSH, POP, ADD (pop off top two elements from stack, add them and push result back onto stack), SUB, MULT, DIV, etc.

Ex. Translate X * Y + Z * W to:

    PUSH X
    PUSH Y
    MULT
    PUSH Z
    PUSH W
    MULT
    ADD
Trace result if X = 2, Y = 7, Z = 3, W = 4.

How do you generate this code?

Write expression in postfix form: operator after operands

E.g. 2 + 3 -> 2 3 +

Representing arithmetic expressions:

General algorithm to convert to postfix notation:
  1. Write expression fully parenthesized.
  2. Recursively move operator after operands, dropping parentheses when done.
E.g.

X * Y + Z * W -> (X * Y) + (Z * W) -> (X Y *) (Z W *) + -> X Y * Z W * +

Note: in Polish notation parentheses no longer needed. Corresponds to reverse Polish calculator.

Once in postfix, easy to generate code as follows:

Above expression compiled as shown earlier: PUSH X, PUSH Y, MULT, ...

Straightforward to write a computer program using stacks to use these instructions (or even the original postfix expression) to calculate values of arithmetic expressions.

Interestingly, algorithm to convert expression to postfix form can either be done recursively (as above) or using a stack.

Notice all operands appear in same order as started - only operations move. Commands to transform above expression (working on it from left to right):

    OUTPUT X
    PUSH *
    OUTPUT Y
    POP and OUTPUT operator
    PUSH +
    OUTPUT Z
    PUSH *
    OUTPUT W
    POP and OUTPUT operator
    POP and OUTPUT operator
Other rules - "(" is always pushed onto stack, ")" causes operators to be popped and output until pop off topmost "(" on stack. )

Big question: When do you push one operator on top of another and when do you pop off topmost operator before pushing on new one?

Answer:     given in terms of precedence of operators!


Run-time stacks on computers

All computers use a run-time stack in order to keep track of procedure, function, or method invocations.

Programs that use stacks can often be rewritten to simply use recursion (since recursion deals with an implicit stack. Hence the maze-running program can be rewritten as follows, where caller is responsible for setting success to false and current to start originally:

public static void runMaze() 
{
    Position origCurrent = current;     // Save orig value  
    current.visit();
    success = current.equals(finish);   // Are we finished?
        
    while (current != null && !success)     
    {                   // Search until success or run out of cells
        current = nextUnvisited();  // Find new cell to visit
        if (current != null)
        {
            current.visit();
            runMaze();  // Try solving maze from new current
            current = origCurrent;      // reset to try again
        }
    }
}