next up previous
Next: About this document Up: My Home Page

Data Structures and Programming
Lecture 3

Steven S. Skiena

Stacks and Queues

The first data structures we will study this semester will be lists which have the property that the order in which the items are used is determined by the order they arrive.

Queues might seem fairer, which is why lines at stores are organized as queues instead of stacks, but both have important applications in programs as a data structure.

Operations on Stacks

The terminology associated with stacks comes from the spring loaded plate containers common in dining halls.

When a new plate is washed it is pushed on the stack.

When someone is hungry, a clean plate is popped off the stack.

A stack is an appropriate data structure for this task since the plates don't care about when they are used!

Maintaining Procedure Calls

Stacks are used to maintain the return points when Modula-3 procedures call other procedures which call other procedures ...

Jacob and Esau

In the biblical story, Jacob and Esau were twin brothers where Esau was born first and thus inherited Issac's birthright. However, Jacob got Esau to give it away for a bowl of soup, and so Jacob went to become a patriarch of Israel.

But why was Jacob justified in so tricking his brother???

Rashi, a famous 11th century Jewish commentator, explained the problem by saying Jacob was conceived first, then Esau second, and Jacob could not get around the narrow tube to assume his rightful place first in line!

Abstract Operations on a Stack

Defining these abstract operations lets us build a stack module to use and reuse without knowing the details of the implementation.

The easiest implementation uses an array with an index variable to represent the top of the stack.

An alternative implementation, using linked lists is sometimes better, for it can't ever overflow. Note that we can change the implementations without the rest of the program knowing!

Declarations for a stack

INTERFACE Stack;                  (*14.07.94 RM, LB*)
(* Stack of integer elements *)

  TYPE ET = INTEGER;              (*element type*)

  PROCEDURE Push(elem : ET);      (*adds element to top of stack*)
  PROCEDURE Pop(): ET;            (*removes and returns top element*)
  PROCEDURE Empty(): BOOLEAN;     (*returns true if stack is empty*)
  PROCEDURE Full(): BOOLEAN;      (*returns true if stack is full*)

END Stack.

Stack Implementation

MODULE Stack;                  (*14.07.94 RM, LB*)
(* Implementation of an integer stack *)

  CONST
    Max = 8;                   (*maximum number of elements on stack*)
    
  TYPE 
    S = RECORD
          info: ARRAY [1 .. Max] OF ET;
          top: CARDINAL := 0;  (*initialize stack to empty*)
        END; (*S*)

  VAR stack: S;                (*instance of stack*)

  PROCEDURE Push(elem:ET) =
  (*adds element to top of stack*)
  BEGIN
    INC(stack.top); stack.info[stack.top]:= elem
  END Push;

  PROCEDURE Pop(): ET =
  (*removes and returns top element*)
  BEGIN
    DEC(stack.top); RETURN stack.info[stack.top + 1]
  END Pop;

  PROCEDURE Empty(): BOOLEAN =
  (*returns true if stack is empty*)
  BEGIN
    RETURN stack.top = 0
  END Empty;

  PROCEDURE Full(): BOOLEAN = (*returns true if stack is full*)
  BEGIN
    RETURN stack.top = Max
  END Full;

BEGIN
END Stack.

Using the Stack Type

MODULE StackUser EXPORTS Main;    (*14.02.95. LB*)
(* Example client of the integer stack *)

  FROM Stack IMPORT Push, Pop, Empty, Full;
  FROM SIO IMPORT Error, GetInt, PutInt, PutText, Nl;
  <* FATAL Error *>               (*suppress warning*)

BEGIN
  PutText("Stack User. Please enter numbers:\n");
  WHILE NOT Full() DO
    Push(GetInt())                (*add entered number to stack*)
  END;
  WHILE NOT Empty() DO
    PutInt(Pop())                 (*remove number from stack and return it*)
  END;
  Nl();
END StackUser.

FIFO Queues

Queues are more difficult to implement than stacks, because action happens at both ends.

The easiest implementation uses an array, adds elements at one end, and moves all elements when something is taken off the queue.

It is very wasteful moving all the elements on each DEQUEUE. Can we do better?

More Efficient Queues

Suppose that we maintaining pointers to the first (head) and last (tail) elements in the array/queue?

Note that there is no reason to explicitly clear previously unused cells.

Now both ENQUEUE and DEQUEUE are fast, but they are wasteful of space. We need a array bigger than the total number of ENQUEUEs, instead of the maximum number of items stored at a particular time.

Circular Queues

Circular queues let us reuse empty space!

Note that the pointer to the front of the list is now behind the back pointer!

When the queue is full, the two pointers point to neighboring elements.

There are lots of possible ways to adjust the pointers for circular queues. All are tricky!

How do you distinguish full from empty queues, since their pointer positions might be identical? The easiest way to distinguish full from empty is with a counter of how many elements are in the queue.

FIFO Queue Interface

INTERFACE Fifo;                 (*14.07.94 RM, LB*)
(* A queue of text elements *)
 
  TYPE ET = TEXT;               (*element type*)
 
  PROCEDURE Enqueue(elem:ET);   (*adds element to end*)
  PROCEDURE Dequeue(): ET;      (*removes and returns first element*)
  PROCEDURE Empty(): BOOLEAN;   (*returns true if queue is empty*)
  PROCEDURE Full(): BOOLEAN;    (*returns true if queue is full*)
 
END Fifo.

Priority Queue Implementation

MODULE Fifo;                      (*14.07.94 RM, LB*)
(* Implementation of a fifo queue of text elements *)
 
  CONST 
    Max = 8;                      (*Maximum number of elements in FIFO queue*)
    
  TYPE     
    Fifo = RECORD
            info: ARRAY [0 .. Max - 1] OF ET;
            in, out, n: CARDINAL := 0;
          END; (*Fifo*)
 
  VAR w: Fifo;                    (*contains a FIFO queue*)
 
  PROCEDURE Enqueue(elem:ET) =
  (*adds element to end*)
  BEGIN
    w.info[w.in]:= elem;          (*stores new element*)
    w.in:= (w.in + 1) MOD Max;    (*increments in-pointer in ring*)
    INC(w.n);                     (*increments number of stored elements*)
  END Enqueue;
 
  PROCEDURE Dequeue(): ET =
  (*removes and returns first element*)
  VAR e: ET;
  BEGIN
    e:= w.info[w.out];             (*removes oldest element*)
    w.out:= (w.out + 1) MOD Max;   (*increments out-pointer in ring*)
    DEC(w.n);                      (*decrements number of stored elements*)
    RETURN e;                      (*returns the read element*)
  END Dequeue;

Utility Routines

 
  PROCEDURE Empty(): BOOLEAN =
  (*returns true if queue is empty*)
  BEGIN
    RETURN w.n = 0;
  END Empty;
 
  PROCEDURE Full(): BOOLEAN =
  (*returns true if queue is full*)
  BEGIN
    RETURN w.n = Max
  END Full;
 
BEGIN
END Fifo.

User Module

MODULE FifoUser EXPORTS Main;          (*14.07.94. LB*)
(* Example client of the text queue. *)
 
  FROM Fifo IMPORT Enqueue, Dequeue, Empty, Full; (* operations of the queue *)
  FROM SIO IMPORT Error, GetText, PutText, Nl;
  <* FATAL Error *>                               (*supress warning*)
 
BEGIN
  PutText("FIFO User. Please enter texts:\n");
  WHILE NOT Full() DO
    Enqueue(GetText())
  END;
  WHILE NOT Empty() DO
    PutText(Dequeue() & "  ")
  END;
  Nl();
END FifoUser.

Other Queues

Double-ended queues - These are data structures which support both push and pop and enqueue/dequeue operations.

Priority Queues(heaps) - Supports insertions and ``remove minimum'' operations which useful in simulations to maintain a queue of time events.

We will discuss simulations in a future class.




next up previous
Next: About this document Up: My Home Page

Steve Skiena
Thu Sep 11 15:55:14 EDT 1997