CSC 161 Grinnell College Fall, 2013
 
Imperative Problem Solving and Data Structures
 
 

Stacks

Abstract

This reading introduces the concept of an Abstract Data Type (ADT) and describes a stack as a specific example.

Acknowledgement

Most of this reading is an edited version of Henry M. Walker, Introduction to Computing and Computer Science with Pascal, Little, Brown, and Company, 1986, Sections 17.1-17.2, 17.4, with programming examples translated from Pascal to C. This material is used with permission from the copyright holder.

Abstract Data Types

When we want to work with data on a general level, we often need to describe two basic characteristics: the data we will be storing, and the operations we will want to perform on these data. In computer science, these two characteristics combine to give the concept of an abstract data type or ADT which allows us to work with data on a conceptual level without worrying about various programming details.

The stack discussed in this lab provides one example of an abstract data type. The queue, discussed in a forthcoming lab, provides a second example.

Stacks

A stack mimics the information that we might keep in a pile on our desk. For example, on our desk, we may keep separate piles for

These piles have several properties. First, each pile contains the same type of information (e.g., bills, magazines, or notes). In addition, for each pile, we can do several tasks.

  1. We can add to the pile by putting information on the top.
  2. We can take the top item off of our pile.
  3. We can read the item on the top.
  4. We can tell if a pile is empty. (There may be nothing at the spot where the pile should be.)

These operations allow us to do all of our normal processing of data at our desk. For example, when we receive bills in the mail, we add them to our pile of bills until payday comes. Then, we take our bills, one at a time, off the top of our pile and pay them until our money runs out.

When discussing these operations, it is customary to call the addition of an item to the top of the pile a Push operation and the deletion of an item from the top a Pop operation.

More formally, a stack is defined as an abstract data type that can store data and that has the following operations:

This specification says nothing about how we will program the various stack operations; rather, it tells us how stacks can be used. We can also infer some limitations on how we can use the data. For example, stack operations allow us to work with only the top item on the stack. We cannot look at other pieces of data lower down in the stack without first using Pop operations to clear away items above the desired one.

A Push operation always puts the new item on top of the stack, and this is the first item returned by a Pop operation. Thus, the last piece of data added to the stack will be the first item removed. For these functions (along with initialize), you have to pass a pointer to the top of the stack and cannot pass in the actual element, because you need the ability to modify the stack itself, not just a copy of the element on top. For other functions which deal with the stack, you can generally pass in either a pointer to the top of the stack, or the actual element itself.

Implementation of Stacks by Arrays


An Array Implementation of a
Stack

Stacks in C

One common implementation of a stack involves the use of an array.

More precisely, we store each piece of data as an element of an array. We place the first data item at one end of the array. Then, for a Push operation, we add a data item to the next array element. For a Pop operation, we return the item at the top of our data, and we record that the top has moved down. This processing requires several parts, including an array (StackArray) of data to store our data items, a variable (topPosition) to keep track of our top element, and a constant (MaxStack) to keep track of the size of our array. Conceptually, this setup fits together as shown in the figure. Note that a stack implementation may have the topPosition as either the top element in the stack, or a pointer to the top empty element. Both implementations of stacks are valid.

With this figure, we trace what happens in our stack operations. We start with topPosition equal to -1, since we have no data in the array. Then, we perform a Push, we increment topPosition by one, and we store our new item in this new topPosition. Similarly, for a Pop operation, we return the item at the topPosition, and we move the topPosition down by one. Finally, for Full or Empty functions, we compare the topPosition with MaxStack-1 or -1, respectively.

Additional details arise because we must check if the stack is full or empty before actually performing a Push or Pop operation, respectively.

Finally, we note that sometimes it is convenient to package the elements together in a struct construction, perhaps with a typedef. For example, the following declarations might be used when the stack is to store strings.

   #define MaxStack 50;  /* MaxStack stands for the size 
                            of all stack arrays */

   typedef struct {
      int topPosition;
      char * stackArray [MaxStack];
   } stringStack;      /* type for a stack of strings */

Implementation of Stacks by Pointers

The previous section on stacks described the use of arrays to implement this abstract data type. That section utilized the following declarations and function prototypes:

   #define MaxStack  50  /* MaxStack stands for the size of all stack arrays */

   typedef struct {
      int topPosition;
      char * stackArray [MaxStack];
   } stringStack;      

  int empty (stringStack stack)
  int full (stringStack stack)      
  void initializeStack (stringStack * stack) 
  char * pop (stringStack *stack) 
  int push (stringStack *stack, char * item) 
  char * top (stringStack stack)

In this section, we implement the same operations using lists and pointers. Further, since applications should consider only the conceptual operations of an abstract data type, not the implementation details, we will be careful that the new code we develop still uses exactly the same procedure and function headers as defined earlier for arrays.

When describing this new approach to a stack, we focus on the specification of the top of the stack, and we consider how to locate subsequent times in the stack after we perform a Pop operation. The following figure shows how such a structure could be organized.

A List/Pointer Implementation of a Stack

In this picture, we store a stack Item in a record with a pointer to the next lower Item on the stack. Also, we use a pointer variable, which specifies the top record.

Now suppose that we decide to store string data within a node. There are at least two choices to store the string data: