Return to the lecture notes index

Lecture #9 (May 31, 2002)

Stacks

In the real world, a stack is a way of organizing something where all of the items are piled on top of one another. We might have a stack of papers on a desk, a stack of books, etc. The easiest way to work with the items that are in the stack is to take off the one on the top and process it. The easiest way to add something to the stack is to put it on the top of the pile.

In computer science, we can implement a stack in a similar way. A stack is a first-in-last-out data structure where we always remove the item which has most recently been added to the stack. This means that in order to remove the first item that we added we first have to remove everything else, which is why we say that it is a first-in-last-out structure.

Let's look at the operations on a stack:

Why Use Stacks?

Much as was the case for queues, stacks are far less powerful than linked lists or vectors. We don't use them because we want more power -- we use them because we want more disciple. We'll talk about many examples of their use, beginning next class.

But for now, let me mention some things that might, or might not, be familiar to you. If not, don't fret -- we'll get there. Sometimes we use stacks to store the intermediate steps in a calculation. Consider, for example, RPN calculators, like the famous HP scientific calculators.

Or, if you remember discussing the "call chain", a.k.a, the "call stack" in your intor class, you might now realize that we can model the calling and returning of methods using stacks. Each time we call a method, we push() it onto the stack. And, each time a method returns, we can pop() if off of the stack. Then, at any time, this stack will model the state of execution of our system.

Implementing Stacks With Vectors

So we need to implement the super-complex behaviors -- "push" and "pop" (we'll ignore "peek" for now, it is dwarfed by comparison). This requires us to add and remove items from the same end of the vector. Although index 0 of the vector might seem like the closest match to the "top" of a stack, we are going to use the other end. We'll see why in a minute.

To add something to the stack, then, we want to add our new item after the last item we put in. The add() or addElement() method of the Vector class does this by default. To remove something from the stack, we will have to remove the rightmost value, but since there is nothing after it we do not have to make any other adjustments to the vector.

How is this better than using index 0 as the "top" of the stack. To insert a new item at index 0, we first need to shift every other item in the vector up by 1 (what was in index 0 will now be in index 1, and so on), so if the vector is large this is quite costly. Similarly, if we remove something from index 0, we need to fill in that spot, so every item in the vector will have to shift down by 1 index. To do this every time we push or pop is wasteful, since adding and removing on the righthand side do not require us to change the vector in any other way.

Implementing A Stack With A Linked-List

Before we can implement a stack with a linked-list, we need to decide which end of the list we want to use as the top of the stack. Do we want to add and remove elements from the head, or do we want to add and remove elements from the tail?

Adding to the head only requires us to rearrange a few references in order to include the new node in the list, as does adding to the tail. Removing from the head only requires us to rearrange a few references in order to remove the first node from the list. Removing from the tail is a little more complicated because we have to set the second-to-last element to be the new tail. Since we cannot go backwards in a singly-linked list, we have to start at the beginning of the list and traverse until we reach the second-to-last node. In a long list, this is time consuming, so we will use the head of the list as the top of the stack.

The following code implements a stack using a linked-list:


/*
 * This class implements a stack using a linked-list.  It has the standard
 * stack behaviors push and pop, as well as a peek method to see the item
 * on the top of the stack without removing it, and an isEmpty method to
 * see test if the stack is empty.
 */
class ListStack
{
   private LinkedList stack;  // stores the items of the stack


   /*
    * Constructor.  It initializes a new LinkedList to be used for the stack
    */
   public ListStack()
   {
      stack = new LinkedList();
   }


   /*
    * This method adds a new item to the top of the stack
    */
   public void push(Object addObj)
   {
      // we are using the head of the linked-list as the top of the
      // stack, so we add to the head
      stack.addHead(addObj);
   }


   /*
    * This method remove the item from the top of the stack
    */
   public Object pop()
   {
      // we are using the head of the linked-list as the top of the
      // stack, so we remove from the head
      return stack.removeHead();
   }


   /*
    * This method returns the item at the top of the stack without
    * removing it
    */
   public Object peek()
   {
      // set the index of the linked-list to be at the head
      stack.resetIndex();

      // return back the item stored at the head
      return stack.getIndexedNode();
   }


   /*
    * This method tests whether or not the stack is currently empty
    *
    * if there is nothing at the head, then there is nothing in
    * the stack
    */
   public boolean isEmpty()
   {
      // set the index of the linked-list to be at the head
      stack.resetIndex();

      // return true if the head of the list is null, false otherwise
      return (null == stack.getIndexedNode());
   }
}