CS136, Lecture 14

        1. Run-time stacks on computers
      1. Stack Implementations
        1. Array-based implementation
        2. Linked list implementation
        3. Analyzing the implementations:

Talked last time about converting from infix to postfix notation using a stack:

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

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 which 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 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
    }
  }
}

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.

See on-line StackArray implementation:

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 makes more sense to implement with Vector (see StackVector) to allow unbounded growth (at cost of occasional O(n) delays).

All operations are O(1) with exception of occasional push and clear, which should replace all entries by null in order to let them be garbage-collected.

Linked list implementation

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

See on-line StackList implementation.

Analyzing the implementations:

Operations: peek, pop, isEmpty all O(1) for Array, Vector, and Linked List implementations. push can be O(n) in worst case for Vector, though average is O(1), other implementations always O(1). clear O(1) for linked list, O(n) for array or vector representation.

Arrays use a fixed amount of space: this wastes space if you reserve too much, while the program won't run if there is too little.

Vector provides more flexibility, but at the cost of occasional significant delays (though average cost of push is O(1))

The linked list implementation has all operations O(1) in worst case, but needs extra space for the links.