Algorithms and OOD (CSC 207 2014F) : Readings

Stacks


Summary: We consider the essential features of stacks, one of the forms of linear structures. We also consider a straightforward implementation of stacks.

Prerequisites: Linear structures, Arrays, Linked structures, Polymorphism, Inheritance

Stack Basics

Now that you understand about linear structures, stacks are a fairly simple abstract data type. Stacks are simply linear structures that implement that last in, first out (also “LIFO”) policy. That is, the value returned by get is the value most recently added to the stack.

An Interface

We might describe the interface for stacks as follows.

package taojava.util;

import java.util.Iterator;

/**
 * A linear structure that follows the last-in, first-out policy.
 *
 * @author Samuel A. Rebelsky
 */
public interface Stack<T>
    extends LinearStructure<T>
{
  /**
   * Add an element to the stack.
   *
   * @param val
   *   the value to add.
   * @pre
   *   !this.isFull()
   * @post
   *   The stack now contains an additional element of val.
   * @exception Exception
   *   If the structure is full.
   */
  public void put(T val)
    throws Exception;

  /**
   * Remove the most recently added element that is still in the
   * stack.
   *
   * @return
   *   val, a value.
   * @pre
   *   !this.isEmpty()
   * @post
   *   The structure contains one fewer copy of val.
   * @post
   *   Every element that remains in the stack was added less recently
   *   than val.
   * @exception Exception
   *   If the structure is empty.
   */
  public T get()
    throws Exception;

  /**
   * Determine what element will next be removed by get.
   *
   * @return
   *   val, a value.
   * @pre
   *   !this.isEmpty()
   * @post
   *   Every other value in the stack was added less recently than val.
   * @exception Exception
   *   If the structure is empty.
   */
  public T peek()
    throws Exception;

  /**
   * Determine if the structure is empty.
   */
  public boolean isEmpty();

  /**
   * Determine if the structure is full.
   */
  public boolean isFull();

  /**
   * Get an iterator that returns all of the elements in some order.
   */
  public Iterator<T> iterator();

  /**
   * Push a value on the stack.  (An alias for put.)
   */
  public void push(T val)
    throws Exception;

  /**
   * Pop a value from the stack.  (An alias for get.)
   */
  public T pop()
    throws Exception;
} // interface Stack<T>

One aspect of this code you may find of interest is that we have said that Stack extends LinearStructure. This extension is similar to the extension you've seen for classes, although it is used primarily for polymorphism. It means that you can use a Stack whereever code expects a LinearStructure

Applications of Stacks

There are a variety of ways in which computer scientists use stacks. At times, they use them simply because they need some linear structure, and stacks are convenient to use. More frequently, they identify problems for which the last-in, first-out policy is particularly appropriate.

One such class of problems involves matching symbols, such as tags in an HTML document or parens in a Scheme program. In essence, whenever you see an opening symbol, such as <b> in an HTML document or ( in a Scheme program, you push it on the stack. When you see a closing symbol, such as </b> in an HTML document or ) in a Scheme program, you pop the value on the stack and compare it to the closing symbol. If they match, you continue on. If they fail to match, you report an error. Clearly, if you encounter an end symbol with an empty stack, there's something seriously wrong with the document or program. Similarly, if the stack contains values at the end, there are also significant problems, since not all opening symbols are matched.

Stacks are also useful for certain kinds of operations. For example, some mathematicians like to use reverse polish notation (RPN), in which the operation follows the operands. In such a system, we would write “add 2 and the product of 3 and 7” as 2 3 7 * +. RPN is useful because you don't have to worry about precedence rules. RPN is also very easy to implement using stacks.

  • Whenever you encounter a value, you push it.
  • Whenever you encounter an operation, pop the parameters and push the result.
  • The value on the top of the stack when you are done is the final result.

Implementing Stacks with Arrays

It is also fairly easy to implement stacks, at least once we have another data structure, such as an array. In an array-based implementation of stacks, we typically store the values as they come in, starting at index 0. When we pop a value, we use the index of the last value added and decrease that value. (Typically, we use a field called top to keep track of where to add values.)

Given that strategy, here's the basic code for put.

this.contents[this.top] = newvalue;
this.top++

Most programmers would express this more concisely as follows.

this.contents[this.top++] = newvalue;

It is equally easy to get the value at the top of the stack: We just reverse those two steps.

this.top--;
return this.contents[this.top];

Again, we can combine these two lines into a single instruction.

return this.contents[--this.top];

Note that when we combine the operations, we use the prefix version of the decrement operation so that this.top is decremented before we use it as an index.

To avoid accidental access to removed data, many programmers make it a habit to clear out the entry in an array whenever we remove a value. Hence, we will need to temporarily store the value to be returned before clearing the cell and then returning that value.

T returnme = this.contents[--this.top];
this.contents[this.top] = null;
return returnme;

Determining whether or not the stack is empty is also easy: The stack is empty only when top is 0.

return (top == 0);

The only hard part is what to do when the stack “fills”. The simplest thing to do is to make a bigger array and copy the values over. The particular code for doing so is left as an exercise for the reader.

A Linked Implementation of Stacks

We can also implement stacks using linked nodes. What is a node? It's a simple data type that contains two parts: a value and a link to the next node.

package taojava.util;

/**
 * Simple nodes for linked structures.
 */
public class Node<T>
{
  // +--------+----------------------------------------------------------
  // | Fields |
  // +--------+

  /**
   * The value stored in the ndoe.
   */
  T value;

  /**
   * The next node.
   */
  Node<T> next;

  // +--------------+----------------------------------------------------
  // | Constructors |
  // +--------------+

  /**
   * Create a new node that contains val and that links to
   * next.
   */
  public Node(T value, Node<T> next)
  {
    this.value = value;
    this.next = next;
  } // Node(T, Node<T>)
} // class Node<T>

For a stack, we just need a pointer to the node at the front of the stack, which we'll call front.

To put something at the front of the stack, we can write

this.front = new Node(val, this.front);

To get something from the front of the stack, we can write

T result = this.front.val;
this.front = this.front.next;
return result;

Ideally, the stack never fills. If it does, we're in pretty big trouble, because we can't even create a small object, which means that we'll also have trouble creating an exception object.

public boolean isFull()
{
  return false;
} // isFull()

We can check if the stack is empty by comparing front to null.

public boolean isEmpty()
{
  return this.front == null;
} // isEmpty()

Terminology

Unfortunately, some bright computer scientists designed the stack ADT before some other bright computer scientists designed the more general linear structure ADT. Hence, the terms that many folks use for the basic stack operations are not put and get, but rather push and pop.

To make our code more general, we will stick with the linear structure terms.

Citation

This reading is based on a similar reading I created as a part of Espresso.