CSC 161 Grinnell College Spring, 2009
 
Imperative Problem Solving and Data Structures
 

Stacks and Queues with Linked Lists

Abstract

This reading describes the implementation of stacks and queues using linked lists.

Acknowledgement

Most of this reading is an edited version of Henry M. Walker, Computer Science 2: Principles of Software Engineering, Data Types, and Algorithms, Little, Brown, and Company, 1989, Sections 6.4 and 7.2, with programming examples translated from Pascal to C. This material is used with permission from the copyright holder.

Implementation of Stacks by Pointers

Our earlier lab on stacks described the use of arrays to implement this abstract data type. That lab 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. The appropriate declarations for a stack with string data are

   typedef stackNode * stringStack;
   struct node {
      char * stackArray [MaxStack];
      struct stackNode * next;
   } stackNode;     

   stringStack stack;

With these declarations, we initialize stack to NULL at the beginning of the program, and the Boolean expression

Stack == Nil

allows us to test for an empty stack. Then the Push, Pop, and Top operations involve inserting, deleting, and reading items at the top of this list structure, respectively. With the use of dynamic storage allocation, there is no need to declare a list size initially, and the Full operation is not needed. However, for compatibility with the earlier array implementation of stacks, this function is still included and will always return False. The outlines of these operations are quite similar to the work in the earlier ones. Also, in the Pop operation, we free the list item once we have returned the appropriate information. The coding details for these operations are shown below.

Once these functions and procedures are defined, we can use them just as we did before. In a program, we must include the appropriate stack declaration (shown earlier in this section); we must write out these procedures; and we must call the Initialization procedure. However, once these details are completed, the compatibility of the procedure and function headers allows us to use Empty, Push, Pop, and Top without change. Thus, while the implementation of stacks has changed dramatically, the use of stacks in applications is identical.

Implementation of Queues with Linked Lists

The second approach for implementing queues resolves some of these queue size problems by using the dynamic storage allocation that is available through the use of pointers. As with the discussion of stacks, we want to retain the same operations and calling formats defined earlier when queues were implemented by arrays. In particular, the queue operations should include the following functions:

   void initializeQueue (stringQueue * queue)
   int empty (stringQueue queue)
   int full (stringQueue queue)
   int enqueue (stringQueue * queue, char* item)
      (returns length of string added or -1 if queue is full)
   char * dequeue (stringQueue * queue)
      (returns string removed from queue)

In this structure, we must work with both ends of the queue, inserting items at the tail and deleting them from the head. Here, we view the queue as ordering items from the head to the tail; the head is the first item we will remove, and the tail is the last item. The following figure shows how this might work.

A List/Pointer Implementation of a Queue

In this picture, the queue consists of a list of records, where each record contains an item of data and each record points to the record that comes after it. In addition, for the overall queue, the items at the front and back of the queue must be specified. The appropriate declarations are

   /* Maximum length of names */
   #define strMax 20

   typedef struct node
   { char data [strMax];
     struct node * next;
   } queueNode;

   typedef struct {
      queueNode * head;
      queueNode * tail;
   } stringQueue;
   
   stringQueue queue;

With these declarations, initialization sets head and tail to Null at the start of the program, and the Boolean expression

   queue.head == NULL;

tests whether the queue is empty. The enqueue operation then proceeds by adding an element at the tail end of the list. Also, the dequeueoperation proceeds by returning the data at the head of the list, moving the head pointer to the next element, and disposing of the old record. Each of these operations also requires some care for processing the special cases when the queue is empty and when it contains only one item.


This document is available on the World Wide Web as

http://www.walker.cs.grinnell.edu/courses/161.fa09/readings/reading-stacks-queues-lists.shtml

created 17 April 2008
last revised 29 April 2009
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.