0% found this document useful (0 votes)
26 views34 pages

03-Chap 03-STACKS AND QUEUES

Uploaded by

Dinesh Kavoor
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views34 pages

03-Chap 03-STACKS AND QUEUES

Uploaded by

Dinesh Kavoor
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

CHAPTER 3

STACKS AND QUEUES

3.1 THE STACK ABSTRACT DATA TYPE

In this chapter we look at two data types that are frequently found in computer science.
These data types, the stack and the queue, are special cases of the more general data
type, ordered list, that we discussed in Chapter 2. Recall thatA = oq, tzi, ••• ,tz„_iisan
ordered list of n > 0 elements. We refer to the ai as atoms or elements that are taken
from some set. The null or empty list, denoted by (), has n = 0 elements. In this section
we begin by defining the ADT Stack and follow with its implementation. In the next
section we look at the queue.
A stack is an ordered list in which insertions and deletions are made at one end
called the top. Given a stack S = {a^, • ■ • , ), we say that aq is the bottom element,
is the top element, and a^ is on top of element 6Z/_i , 0 < i < n. The restrictions on
the stack imply that if we add the elements A, B, C, D, E to the stack, in that order, then E
is the first element we delete from the stack. Figure 3.1 illustrates this sequence of
operations. Since the last element inserted into a stack is the first element removed, a
stack is also known as a Last-In-Eirst-Out (LlEO) list.

Example 3.1 [System stack}


*. Before we discuss the stack ADT, we look at a special
stack, called the system stack, that is used by a program at run-time to process function
calls. Whenever a function is invoked, the program creates a structure, referred to as an
activation record or a stack frame, and places it on top of the system stack. Initially, the

101
102 Stacks And Queues

£ *-top
D top D D *-top
C <-top C C C
B *-top B B B B
A *-top A A A A A

Figure 3.1: Inserting and deleting elements in a stack

activation record for the invoked function contains only a pointer to the previous stack
frame and a return address. The previous stack frame pointer points to the stack frame of
the invoking function, while the return address contains the location of the statement to
be executed after the function terminates. Since only one function executes at any given
time, the function whose stack frame is on top of the system stack is chosen. If this func­
tion invokes another function, the local variables, except those declared static, and the
parameters of the invoking function are added to its stack frame. A new stack frame is
then created for the invoked function and placed on top of the system stack. When this
function terminates, its stack frame is removed and the processing of the invoking func­
tion, which is again on top of the stack, continues. A simple example illustrates this pro­
cess. (We refer the reader who wants a more detailed discussion of stack frames to
Holub’s book on compiler design cited in the References and Selected Readings section.)
Assume that we have a main function that invokes function al. Figure 3.2(a)
shows the system stack before al is invoked; Figure 3.2(b) shows the system stack after
al has been invoked. Frame pointer fp is a pointer to the current stack frame. The sys­
tem also maintains separately a stack pointer, sp, which we have not illustrated.
Since all functions are stored similarly in the system stack, it makes no difference
if the invoking function calls itself. That is, a recursive call requires no special strategy;
the run-time program simply creates a new stack frame for each recursive call. How­
ever, recursion can consume a significant portion of the memory allocated to the system
stack; it could consume the entire available memory. □

Our discussion of the system stack suggests several operations that we include in
the ADT specification (Structure 3.1).
The easiest way to implement this ADT is by using a one-dimensional array, say,
stack [MAX-STACK-SIZE\, where MAX STACK SIZ.E is the maximum number of
entries. The first, or bottom, element of the stack is stored in the second in
5'facA; [1], and the zth in stack [z-1]. Associated with the array is a variable, top, which
points to the top element in the stack. Initially, top is set to -1 to denote an empty stack.
Given this representation, we can implement the operations in Structure 3.1 as follows.
Notice that we have specified that element is a structure that consists of only a key field.
Ordinarily, we would not create a structure with a single field. However, we use element
in this and subsequent chapters as a template whose fields we may add to or modify to
The Stack Abstract Data Type 103

old frame pointer fp

return address al

local uariables

old frame pointer fp old frame pointer

return address main return address


(a) (b)

Figure 3.2: System stack after function call

meet the requirements of our application.

Stack CrQateS(max-stack-size') ::=

#define MAX—STACK—SIZE 100 *


/
maximum stack size
/
*
typedef struct {
int key;
/* other fields */
} element;
element stack[MAX—STACK—SIZE];
int top - -1;

Boolean IsEmpty(Stack) ::= top 0;

Boolean IsFulI(Stack) ::= top = MAX-STACK-SIZE-l;

The IsEmpty and IsFull operations are simple, and we will implement them
directly in the add (Program 3.1) and delete (Program 3.2) functions. In each of these
functions we have passed in the top of the stack as a parameter. The stack is kept global
and "hidden" because we want to reinforce the concept that the only access to the stack
is through the pointer to the top. The functions are short and require little explanation.
Function add checks to see if the stack is full. If it is, it calls stack-full. Although we
haven’t implemented stack-full, minimally it should print an error message to the stan­
dard error device (stderr). If the stack is not full, we increment top and add item to the
stack. Implementation of the delete operation parallels that of the add operation. For
104 Stacks And Queues

structure Stack is
objects: a finite ordered list with zero or more elements.
functions:
for all stack e Stack, item e element, max-stack-size e positive integer
Stack CrQ£itQS(max-stack-size)
create an empty stack whose maximum size is max-stack-size
Boolean IsFulI(5/£/cZ:, inaxstack-size) ;; =
if (number of elements in stack == max-stack-size)
return TRUE
else return FALSE
Stack A(i6(stack, item) ::=
if (IsFull(5fac^)) stack -full
else insert item into top of stack and return
Boolean IsEmptyC^rac/:) ::=
if (stack == CrQSitQS(max-stack-size))
return TRUE
else return FALSE
Element DQiete(stack) ::=
if (IsEmpty(5?ac^)) return
else remove and return the item on the top of the stack.

Structure 3.1: Abstract data type Stack

deletion, the stack-empty function should print an error message and return an item of
type element with a key field that contains an error code. Typical function calls would be
add(&top, item); and item = deletef&top);. Notice that in both function calls we pass in
the address of top. If we do not pass in the address the changes made to top by add or
delete will not percolate back to the main program.

void add(int *top, element item)


{
*
/ add an item to the global stack */
if top
*
( = MAX-STACK-SIZE-l) {
stack—full() ;
return;
}
stack[++
top]
* item;
}

Program 3.1: Add to a stack


The Stack Abstract Data Type 105

element delete(int top)


{
*
/ return the top element from the stack */
if {*
top == -1)
return stack—empty(); / returns an error key *
/
top)
return stack[(
* —];
}

Program 3.2: Delete from a stack

EXERCISES

1. Implement the stack-empty and stack-full functions.


2. Using Figures 3.1 and (3.2) as examples, show the status of the system stack after
each function call for the iterative and recursive functions to compute binomial
coefficients (Exercise 9, Section 1.2). You do not need to show the stack frame
itself for each function call. Simply add the name of the function to the stack to
show its invocation and remove the name from the stack to show its termination.
3. The Fibonacci sequence is; 0, 1, 1,2, 3, 5, 8, 13, 21, 34, ■ ■ ■

It is defined as Fq = 0, Fj = 1, and F, = F,-_i + Fj_2, i > 2

Write a recursive function, fibon (n), that returns the nth fibonacci number. Show
the status of the system stack for the call fibon (4) (see Exercise 2). What can you
say about the efficiency of this function?
4. Consider the railroad switching network given in Figure 3.3. Railroad cars num­
bered 0, 1, • • • , n-\ are at the right. Each car is brought into the stack and
removed at any time. For instance, if n = 3, we could move in 0, move in 1. move
in 2, and then take the cars out, producing the new order 2, 1,0. For n = 3 and n =
4, what are the possible permutations of the cars that can be obtained? Are any
permutations not possible?

3.2 THE QUEUE ABSTRACT DATA TYPE

A queue is an ordered list in which all insertions take place at one end and all deletions
take place at the opposite end. Given a queue Q = (<7(), Oi, • • • , is the front
element, is the rear element, and «, + ] is behind 0< i < n-}. The restrictions on
a queue imply that if we insert A, B, C, D, in that order, then A is the first element deleted
from the queue. Figure 3.4 illustrates this sequence of events. Since the first element
inserted into a queue is the first element removed, queues are also known as First-In-
First-Out (FIFO) lists. The ADT specification of the queue appears in Structure 3.2.
106 Stacks And Queues

4 4 4 4

4 0, 1, 2, n-1

Figure 3.3 : Railroad switching network

D rear
C rear C D rear
B rear B B C
A «- rear A front A <- front A <- front B «- front
front

Figure 3.4 : Inserting and deleting elements in a queue

The representation of a queue in sequential locations is more difficult than that of


the stack. The simplest scheme employs a one-dimensional array and two variables,
front and rear. Given this representation, we can define the queue operations in Struc­
ture 3.2 as:

Queue CTQa.tQQ(max-queue-size) ::=


#define MAX-QUEUE —SIZE 100 *
/
Maximum queue size
/
*
typedef struct {
int key;
/* other fields */
} element;
element queue[MAX—QUEUE—SIZE];
int rear = -1;
int front = -1 ;
Boolean IsEmptyQC^wewe) ::= front rear
The Queue Abstract Data Type 107

structure Queue is
objects: a finite ordered list with zero or more elements.
functions:
for all queue g Queue, item G element, max-queue-size G positive integer
Queue CrQateQ^max—queue—size) ::=
create an empty queue whose maximum size is max-queue-size
Boolean IsFullQ(^wewe, max-queue-size) :; =
if (number of elements in queue == max-queue-size)
return TRUE
else return FALSE
Queue AddQ(queue, item) ::=
if (IsFullQ(^M^Mc)) queue - full
else insert item at rear of queue and return queue
Boolean IsEmptyQ(^Mewc) ::=
if {queue == CreateQ^max—queue—size))
return TRUE
else return FALSE
Element DeleteQ(^MeMc) ::=
if (IsEmptyQ(^MCMe)) return
else remove and return the item at front of queue.

Structure 3.2: Abstract data type Queue

Boolean IsFullQ(^MeMe) ::= rear == MAX—QUEUE—SIZE-1

Since the IsEmptyQ and IsFullQ operations are quite simple, we again implement
them directly in the addq (Program 3.3) and deleteq (Program 3.4) functions. Functions
addq and deleteq are structurally similar to add and delete on stacks. While the stack
uses the variable top in both add and delete, the queue increments rear in addq and front
in deleteq. Typical function calls would be addq(&rear, item); and item =
deleteq(&front, rear);. Notice that the call to addq passes in the address of rear. We do
this so that the modification to rear is permanent. Similarily, in the call to deleteq we
pass in the address of front so that the modification to front is peimanent. We do not
pass in the address of rear since deleteq does not modify rear, but it does use rear to
check for an empty queue.
This sequential representation of a queue has pitfalls that are best illustrated by
example.

Example 3.2 [Job scheduling]'. Queues are frequently used in computer programming,
and a typical example is the creation of a job queue by an operating system. If the
operating system does not use priorities, then the jobs are processed in the order they
enter the system. Figure 3.5 illustrates how an operating system might process jobs if it
108 Stacks And Queues

used a sequential representation for its queue.

void addq(int *rear, element item)


{
*
/ add an item to the queue
if rear
*
( MAX-QUEUE-SIZE-1) {
queue—full() ;
return;
}
rear]
*
queue[++ 11 em ;
}

Program 3.3: Add to a queue


element deleteq(int front, int rear)
{
/
* remove element at the front of the queue */
if front
*
( == rear)
return queue—empty{); *
/
return an error key
return queue[++
front]
* ;
}

Program 3.4: Delete from a queue

front rear 2(0] e[i] era era Comments


-1 -I queue is empty
-1 0 JI Job 1 is added
-1 1 JI J2 Job 2 is added
-1 2 JI J2 J3 Job 3 is added
0 2 J2 J3 Job 1 is deleted
1 2 J3 Job 2 is deleted

Figure 3.5 : Insertion and deletion from a sequential queue

It should be obvious that as jobs enter and leave the system, the queue gradually
shifts to the right. This means that eventually the rear index equals MAX-QUEVESIZE
- I, suggesting that the queue is full. In this case, queue-full should move the entire
queue to the left so that the first element is again at queue [0] and front is at - 1. It
should also recalculate rear so that it is correctly positioned. Shifting an array is very
The Queue Abstract Data Type 109

time-consuming, particularly when there are many elements in it. In fact, queue-full has
a worst case complexity of O^MAX-QUEUESIZE). □
We can obtain a more efficient queue representation if we regard the array
queue[MAX-QUEUE~SIZE^ as circular. In this representation, we initialize front and
rear to 0 rather than -1. The front index always points one position counterclockwise
from the first element in the queue. The rear index points to the current end of the
queue. The queue is empty front - rear. Figure 3.6 shows empty and nonempty cir­
cular queues for MAX-QUEUESIZE = 6. Figure 3.7 illustrates two full queues for
MAX-QUEUE-SIZE = 6. While these have space for one more element, the addition of
such an element will result in/ronr = rear and we won’t be able to distinguish between
an empty and a full queue. So, we adopt the convention that a circular queue of size
MAX-QUEUE-SIZE will be permitted to hold at most MAX-QUEUE-SIZE - 1 ele­
ments.

EMPTY QUEUE

[21 [31 [21

[11 [41 [11 Ml

[01 [51 LOl

front = 0 front = 0
rear 0 rear 3

Figure 3.6: Empty and nonempty circular queues

Implementing addq and deleteq for a circular queue is slightly more difficult since
we must assure that a circular rotation occurs. This is attained by using the modulus
operator. The circular rotation of the rear index in addq (Program 3.5) occurs in the
statement:

*rear = (**rear+l) % MAX-QUEUE-SIZE;

Notice that we rotate rear before we place the item in queue[rear]. Similarly, in deleteq
(Program 3.6), we rotate front with the statement:

*front = (*front+l) % MAX-QUEUE-SIZE;


110 Stacks And Queues

FULL QUEUE FULL QUEUE

t3]

J8 jg
[4] [4]
[13 [13 J7

J5
[53
[03 [03

front = 0 front = 4
rear = 5 rear = 3

Figure 3.7: Full circular queues

and then we remove the item.

*
void addq(int front, int rear, element item)
{
*
/ add an item to the queue */
Q,
r*
*rear - (
*
fr ear+l ) % MAX—QUEUE—SIZE;
ear+l)
if (front *rear) {
queue_full(rear); *
/ reset rear and print error
/
*
return;
}
queue[*
rear ] i t em ;
}

Program 3.5: Add to a circular queue

Observe that the test for a full queue in addq and the test for an empty queue in
deleteq are the same. In the case of addq, however, when front = *rear is evaluated and
found to be true, there is actually one space free {queue[rear}} since the first element in
the queue is not at queue[front} but is one position clockwise from this point. As
remarked earlier, if we insert an item here, then we will not be able to distinguish
between the cases of full and empty, since the insertion would leave front equal to rear.
To avoid this we signal queue -full, thus permitting a maximum of MAX-QUEUE-SIZE
~ 1 rather than MAX-QUEUE-SIZE elements in the queue at any time. We leave the
implementation of queue-full as an exercise.
The Queue Abstract Data Type 111

element deleteq(int *front, int rear)


{
element item;
*
/ remove front element from the queue and put it in
item */
if (front
* = rear)
return <
queue_empty(); /
* queue_emptY returns an
error key• *
/
*front - (
f
*
( front + 1)
* ront+l) % MAX—QUEUE—SIZE ;
return queue[
* front];
}

Program 3.6: Delete from a circular queue

The queue-full and queue-empty functions have been used without explanation.
Their implementation depends on the particular application. If the intention is to keep
processing and to next delete an element, queue-full should restore the rear pointer to its
previous value. We have suggested this strategy in our call to queue-full. Similarly,
queue-empty should return an item with an error key that can be checked by the main
program.

EXERCISES

1. Implement the queue-full and queue-empty functions for the noncircular queue.
2. Implement the queue-full and queue-empty functions for the circular queue.
3. Using the noncircular queue implementation, produce a series of adds and deletes
that requires Q{MAX-QUEUE-SIZEA) for each add. (Hint: Start with a full
queue.)
4. A double-ended queue (deque) is a linear list in which additions and deletions may
be made at either end. Obtain a data representation mapping a deque into a one­
dimensional array. Write functions that add and delete elements from either end
of the deque.
5. We can maintain a linear list circularly in an array, circle [MAX-SIZE]. We set up
front and rear indices similar to those used for a circular queue.
(a) Obtain a formula in terms of front, rear, and MAX-SIZE for the number of
elements in the list.
112 Stacks And Queues

(b) Write a function that deletes the Uh element in the list.


(c) Write a function that inserts an element, item, immediately after the hh ele­
ment.
(d) What is the time complexity of your functions for (b) and (c)?

3.3 A MAZING PROBLEM

Mazes have been an intriguing subject for many years. Experimental psychologists train
rats to search mazes for food, and many a mystery novelist has used an English country
garden maze as the setting for a murder. We also are interested in mazes since they
present a nice application of stacks. In this section, we develop a program that runs a
maze. Although this program takes many false paths before it finds a correct one, once
found it can correctly rerun the maze without taking any false paths.
In creating this program the first issue that confronts us is the representation of the
maze. The most obvious choice is a two dimensional array in which zeros represent the
open paths and ones the baiTiers. Figure 3.8 shows a simple maze. We assume that the
rat starts at the top left and is to exit at the bottom right. With the maze represented as a
two-dimensional array, the location of the rat in the maze can at any time be described
by the row and column position. If X marks the spot of our current location,
maze[row}[col], then Figure 3.9 shows the possible moves from this position. We use
compass points to specify the eight directions of movement: north, northeast, east,
southeast, south, southwest, west, and northwest, or N, NE, E, SE, S, SW, W, NW.
We must be careful here because not every position has eight neighbors. If
[row,col} is on a border then less than eight, and possibly only three, neighbors exist. To
avoid checking for these border conditions we can surround the maze by a border of
ones. Thus an m x p maze will require an (m +2) x (p +2) array. The entrance is at posi­
tion r 1][1 ] and the exit at [w][p].
Another device that will simplify the problem is to predefine the possible direc­
tions to move in an array, move, as in Figure 3.10. This is obtained from Figure 3.9. We
represent the eight possible directions of movement by the numbers from 0 to 7. For
each direction, we indicate the vertical and horizontal offeet. The C declarations needed
to create this table are:

typedef struct {
short int vert;
short int horiz;
} offsets;
offsets move[8]; array
*
/ of moves for each direction
/
*

We assume that move is initialized according to the data provided in Figure 3.10.
This means that if we are at position, maze[row}[col}, and we wish to find the position of
the next move, maze[next-row\[next~col\, we set:
A Mazing Problem 113

I
entrance 0 1 0 0 0 1 10 0 0 1 I 1 I1 1
1 0 0 0 1 1 0 1 1 10 0 111 1
0 1 10 0 0 0 1 1 110 0 11
1 10 11110 1 10 110 0
I 10 10 0 10 1 1 1 I 1 1 I 1
0 0 I 10 11 10 10 0 10 1
0 1 ! 1 10 0 11111111
1 1
0 0 1 1 0 1 10 111110 ] 1
1 1 0 0 0 1 10 11 0 0 0 0 0
0 0 111 1 10 0 0 1111 0
0 10 0 1 1 1 1 1 0 I 1 1 1 0 exit
J

Figure 3.8: An example maze

NW N NE
[row— l][col— 1] [row— [row-1 ][col-4-1 ]

W E
[row] [col— 1] X [row][col+ 1]

row] [col]

[row+ l][col— 1] [row-1-1] [col] [row-1-1 ][col-l-1]

SW S SE

Figure 3.9: Allowable moves


114 Stacks And Queues

Name Dir move[dir], vert move[dir].horiz


N IT -1
NE 1 -1 1
E 2 0 1
SE 3 1 1
S 4 I 0
SW 5 1 -1
W 6 0 -1
NW 7 -1 -1

Figure 3.10 : Table of moves

next—row = row + move[dir].vert;


next—col = col + move[dir] .horiz;

As we move through the maze, we may have the choice of several directions of
movement. Since we do not know which choice is best, we save our current position and
arbitrarily pick a possible move. By saving our current position, we can return to it and
try another path if we take a hopeless path. We examine the possible moves starting from
the north and moving clockwise. Since we do not want to return to a previously tried
path, we maintain a second two-dimensional array, mark, to record the maze positions
already checked. We initialize this array’s entries to zero. When we visit a position,
maze[raw][col], we change mark[row][cal] to one. Program 3.7 is our initial attempt at a
maze traversal algorithm. EXIT_ROW and EXIT_COL give the coordinates of the maze
exit.
Although this algorithm describes the essential processing, we must still resolve
several issues. Our first concern is with the representation of the stack. Examining Pro­
gram 3.7, we see that the stack functions created in Section 3.2 will work if we redefine
element as:

#define MAX-STACK-SIZE 100 *


/
maximum stack size
/
*
typedef struct {
short int row;
short int col;
short int dir;
} element;
element stack[MAX-STACK-SIZE];
A Mazing Problem 115

initialize a stack to the maze's entrance coordinates and


direction to north;
while (stack is not empty) {
/
* move to position at top of stack *
/
<row,col,dir> - delete from top of stack;
while (there are more moves from current position) {
<next—row, next—col = coordinates of next move;
dir = direction of move;
if ( (next—row == EXIT—ROW) && (next—col EXIT-COL))
success;
if (maze[next—row][next—col] == 0 &&
mark[next —row][next—col] 0) {
/* legal move and haven't been there */
mark[next —row] [next—col] = 1;
/
* save
: current position and direction */
add <row,col,dir to the top of the stack;
row = next—row;
col = next—col;
dir = north;
}
}
}
printf ("No path foundin’’);

Program 3.7: Initial maze algorithm

We also need to determine a reasonable bound for the stack size. Since each posi­
tion in the maze is visited no more than once, the stack need have only as many positions
as there are zeroes in the maze. The maze of Figure 3.11 has only one entrance to exit
path. When searching this maze for an entrance to exit path, all positions (except the
exit) with value zero will be on the stack when the exit is reached. Since, an m x p
maze, can have at most mp zeroes, it is sufficient for the stack to have this capacity.
Program 3.8 contains the maze search algorithm. We assume that the arrays, maze,
mark, move, and stack, along with the constants EXIT-ROW, EXIT-COL, TRUE, and
EALSE, and the variable, top, are declared as global. Notice that path uses a variable
found that is initially set to zero (i.e., EALSEf. If we find a path through the maze, we set
this variable to TRUE, thereby allowing us to exit both while loops gracefully.

Analysis of path: The size of the maze determines the computing time of path. Since
each position within the maze is visited no more than once, the worst case complexity of
the algorithm is O(mp) where m and p are, respectively, the number of rows and columns
of the maze. □
116 Stacks And Queues

r 0 0 0 0 0 11
111110
I 0 0 0 0 1
0 11111
10 0 0 0 1
111110
1 0 0 0 0 1
0 1 1 1 1 I
10 0 0 0 0

Figure 3.11 : Simple maze with a long path

EXERCISES

1. Describe how you could model a maze with horizontal and vertical walls by a
matrix whose entries are zeroes and ones. What moves are permitted in your
matrix model? Provide an example maze together with its matrix model.
2. Do the previous exercise for the case of mazes that have walls that are at 45 and
135 degrees in addition to horizontal and vertical ones.
3. What is the maximum path length from start to finish for any maze of dimensions
rows X columns?
4. (a) Find a path through the maze of Figure 3.8.
(b) Trace the action of function path on the maze of Figure 3.8. Compare this to
your own attempt in (a).
5. § [Programming project] Using the information provided in the text, write a com­
plete program to search a maze. Print out the entrance to exit path if successful.

3.4 EVALUATION OF EXPRESSIONS

3.4.1 Introduction

The representation and evaluation of expressions is of great interest to computer scien­


tists. As programmers, we write complex expressions such as:
{{rear + 1 = =front} I I {{rear==MAX-QUEUE^SIZE - 1) && ! from}} (3.1)
or complex assignment statements such as:
c
x-a/b-c -\-d^e-a
* (3.2)
If we examine expression (3.1), we notice that it contains operators (==, II,
&&, !), operands {rear, front, MAX QUEUE SIZE), and parentheses. The same is true
of the statement (3.2), although the operands and operators have changed, and there are
Evaluation Of Expressions 117

void path(void)

output a path through the maze if such a path exists


int i, row, col, next—row, next—col, dir, found = FALSE;
element position;
mark[l][1] 1; top = 0;
stack[0].row = 1; stack[0].col 1; stack[0] .dir = 1;
while (top -1 && 'found) {
position = delete(&top);
row = position.row; col = position.col;
dir = position.dir;
while (dir < 8 && !found) {
/■^ move in direction dir */

next—row row + move[dir].vert;
next—col col + move[dir].horiz;
if (next-row -- EXIT-ROW && next-col == EXIT-COL)
found : TRUE ;
else if ( 'maze[next—row][next—col] &&
! mark[next—row] [next—col]) {
mark[next—row] [next—col] = 1;
position.row = row; position.col = col;
position.dir = ++dir;
add(S£top, position) ;
row = next—row; col = next—col; dir 0;
}
else ++dir;

if (found) {
printf("The path is:\n");
printf("row col\n");
for (i = 0; i <= top; i++)
printf("%2d%5d", stack[i] .row, stack[i] .col) ;
printf (" %2d%5d\n", rov!, col) ;
printf("%2d%5d\n", EXIT-ROW,EXIT-COL) ;

else printf("The maze does not have a path\n");


}

Program 3.8: Maze search function


118 Stacks And Queues

no parentheses.
The first problem with understanding the meaning of these or any other expres­
sions and statements is figuring out the order in which the operations are performed. For
instance, assume that a = 4, b = c = 2, t/=e = 3in statement (3.2). We want to find the
value of X. Is it

((4/2) -2) + (3 * 3) - (4 * 2)
=0+9-8
= 1
or
(4/(2-2 + 3)) * (3 - 4) * 2
= (4/3) *
(-!) 2
= —2.66666 ' •'

Most of us would pick the first answer because we know that division is carried out
before subtraction, and multiplication before addition. If we wanted the second answer,
we would have written (3.2) differently, using parentheses to change the order of evalua­
tion:
x=((a/(b -c +J))
(e
* *
-a)
c 03)
Within any programming language, there is a precedence hierarchy that deter­
mines the order in which we evaluate operators. Figure 3.12 contains the precedence
hierarchy for C. We have arranged the operators from highest precedence to lowest.
Operators with the same precedence appear in the same box. For instance, the highest
precedence operators are function calls, array elements, and structure or union members,
while the comma operator has the lowest precedence. Operators with highest pre­
cedence are evaluated first. The associativity column indicates how we evaluate opera­
tors with the same precedence. For instance, the multiplicative operators have left-to-
right associativity. This means that the expression a^blc%dle is equivalent to
)lc)%d)le). In other words, we evaluate the operator that is furthest to the left first. With
right associative operators of the same precedence, we evaluate the operator furthest to
the right first. Parentheses are used to override precedence, and expressions are always
evaluated from the innermost parenthesized expression first.

3.4.2 Evaluating Postfix Expressions

The standard way of writing expressions is known as infix notation because in it we


place a binary operator in-between its two operands. We have used this notation for all
of the expressions written thus far. Although infix notation is the most common way of
writing expressions, it is not the one used by compilers to evaluate expressions. Instead
compilers typically use a parenthesis-free notation referred to as postfix. In this notation,
each operator appears after its operands. Figure 3.13 contains several infix expressions
and their postfix equivalents.
Evaluation Of Expressions 119

Token Operator Precedence 1 Associativity


0 function call 17 left-to-right
[] array element
struct or union member
increment, decrement 16 left-to-right
++ decrement, increment'^ 15 right-to-left
I logical not
one’s complement
-+ unary minus or plus
& * address or indirection
sizeof size (in bytes)
(type) type cast 14 right-to-left
* ! % multiplicative 13 left-to-right
+ binary add or subtract 12 left-to-right
shift 11 left-to-right
relational 10 left-to-right

!- equality 9 left-to-right
& bitwise and 8 left-to-right
bitwise exclusive or 7 left-to-right
I bitwise or 6 left-to-right
&«& logical and 5 left-to-right
II logical or 4 left-to-right
conditional 3 right-to-left
=
+= -= /= * %= assignment 2 right-to-left
= »= &= 1=
comma 1 left-to-right

1. The precedence column is taken from Harbison and Steele.


2. Postfix form
3. Prefix form

Figure 3.12: Precedence hierarchy for C


120 Stacks And Queues

Before writing a function that translates expressions from infix to postfix, we


tackle the easier task of evaluating postfix expressions. This evaluation process is much
simpler than the evaluation of infix expressions because there are no parentheses to con­
sider. To evaluate an expression we make a single left-to-right scan of it. We place the
operands on a slack until we find an operator. We then remove, from the stack, the
correct number of operands for the operator, perform the operation, and place the result
back on the stack. We continue in this fashion until we reach the end of the expression.
We then remove the answer from the top of the stack. Figure 3.14 shows this processing
when the input is the nine character string 6 2/3-4 2
+.
*

Infix Postfix
2+3
4
* 2 3 4* +
a^b+5 *5
f2Z? +
1 2+7
*
a/c
b
* ab^c/
{{a/(b -c +d')Y{e -a^^c abc ~d+/ea-^c^
a/b-c -\-d
*
e-a^c *
ab/c-de
+ac^-

Figure 3.13: Infix and postfix notation

Token Stack Top


10]_____ _ [1] [2]
6 6 0
2 6 2 1
/ 6/2 0
3 6/2 3 1
6/2-3 0
4 6/2-3 4 1
2 6/2-3 4 2 2
* 6/2-3 4
2
* 1
+ 6/2-3+4
2
* 0

Figure 3.14: Postfix evaluation

We now consider the representation of both the stack and the expression. To sim­
plify our task we assume that the expression contains only the binary operators +, -, *, I,
and % and that the operands in the expression are single digit integers as in Figure 3.14.
This permits us to represent the expression as a character array. The operands are stored
on a stack of type int until they are needed. The stack is represented by a global array
Evaluation Of Expressions 121

accessed only through top. The complete declarations are:

#define MAX—STACK—SIZE 100 maximuTn


*
/ stack size
/
*
#define MAX—EXPR—SIZE 100 max
*
/ size of expression
/
*
typedef enum {Iparen ,rparen, plus, minus, times, divide,
mod, eos, operand} precedence;
int stack[MAX—STACK—SIZE]; / * global stack */
char expr[MAX-EXPR-SIZE]; /* input string */

The declarations include an enumerated type, precedence, that lists the operators
by mnemonics. Although we will use it to process tokens (operators, operands, and
parentheses) in this example, its real importance becomes evident when we translate
infix expressions into postfix ones. Besides the usual operators, the enumerated type also
includes an end-of-string {eos} operator.
The function eval (Program 3.9) contains the code to evaluate a postfix expression.
Since an operand {symbol} is initially a character, we must convert it into a single digit
integer. We use the statement, symbol - ’O’, to accomplish this task. The statement
takes the ASCII value of symbol and subtracts the ASCII value of ’O’, which is 48, from
it. For example, suppose symbol = ’1’. The character, ’I’, has an ASCII value of 49.
Therefore, the statement symbol - ’0’ produces as result the number 1.
We use an auxiliary function, get-token (Program 3.10), to obtain tokens from the
expression string. If the token is an operand, we convert it to a number and add it to the
stack. Otherwise, we remove two operands from the stack, perform the specified opera­
tion, and place the result back on the stack. When we have reached the end of expres­
sion, we remove the result from the stack.

3.4.3 Infix To Postfix

We can describe an algorithm for producing a postfix expression from an infix one as fol­
lows:
(1) Fully parenthesize the expression.
(2) Move all binary operators so that they replace their corresponding right
parentheses.
(3) Delete all parentheses.
For example, a/b-c *
e-a
+d
c when fully parenthesized becomes:

Performing steps 2 and 3 gives:

ah/c~de^^ac^-
122 Stacks And Queues

int eval(void)
{
evaluate a postfix expression, expr, maintained as a
global variable. '\0' is the the end of the expression.
The stack and top of the stack are global variables,
get—token is used to return the tokentype and
the character symbol. Operands are assumed to be single
character digits ■^ /
precedence token;
char symbol;
int opl, op2;
int n = 0; //
* counter for the expression string
int top = -1;
token - get—token(^symbol, &n);
while (token != eos) {
if (token == operand)
add(&:top, symbol-'O'); !* stack insert */
else {
/
* remove two operands,perform operation, and
return result to the stack */
op2 = delete(&top) ; stack
*
/ delete */
opl = delete(&top) ;
switch(token) {
case plus: add{&top,opl+op2 );
break;
case minus: add(&top, opl-op2);
break;
case times: add(&top, op2);
*
opl
break;
case divide: add(&top,opl/op2);
break;
case mod: add(&top, opl%op2);
}
}
token = get—token (Scsymbol, &n) ;
}
return delete(&top) ; /* return result
}

Program 3.9: Function to evaluate a postfix expression


Evaluation Of Expressions 123

precedence get—token(char *symbol, int *n)


{
*
/ get the next token, symbol is the character
representation, which is returned, the token is
represented by its enumerated value, which
is returned in the function name */
*symbol = expr[(*
n) ++];
switch (
* symbol) {
case ' (' : return Iparen;
case ')' : return rparen;
case ' +' : return plus;
case : return minus;
case '/' : return divide;
case ' * > : return times;
/ Q, !
case 'O : return mod;
case : return eos;
default : return operand; /* no error checking.
default is operand */
}
}

Program 3.10: Function to get a token from the input string

Although this algorithm works well when done by hand, it is inefficient on a com­
puter because it requires two passes. The first pass reads the expression and
parenthesizes it, while the second moves the operators. Since the order of operands is
the same in infix and postfix, we can form the postfix equivalent by scanning the infix
expression left-to-right. During this scan, operands are passed to the output expression
as they are encountered. However, the order in which the operators are output depends
on their precedence. Since we must output the higher precedence operators first, we save
operators until we know their correct placement. A stack is one way of doing this, but
removing operators correctly is problematic. Two examples illustrate the problem.

Example 3.3 [Simple expression]: Suppose we have the simple expression a+b^c,
which yields abc
+
* in postfix. As Figure 3.15 illustrates, the operands are output
immediately, but the two operators need to be reversed. In general, operators with
higher precedence must be output before those with lower precedence. Therefore, we
stack operators as long as the precedence of the operator at the top of the stack is less
than the precedence of the incoming operator. In this particular example, the unstacking
occurs only when we reach the end of the expression. At this point, the two operators
are removed. Since the operator with the higher precedence is on top of the stack, it is
removed first. □
124 Stacks And Queues

Token Slack Top Output


[0] [1] [2]
a -1 a
+ + 0 a
b 0 ab
* + * 1 ab
c 4- * 1 abc
eos -1 abc^-\-

Figure 3.15 : Translation of a +b^c to postfix

Example 3.4 [Parenthesized expression}


*. Parentheses make the translation process
more difficult because the equivalent postfix expression will be parenthesis-free. We use
as our example the expression which yields in postfix. Figure
3.16 shows the translation process. Notice that we stack operators until we reach the
right parenthesis. At this point we unstack until we reach the corresponding left
parenthesis. We then delete the left parenthesis from the stack. (The right parenthesis is
never put on the stack.) This leaves us with only the remaining in the infix expres­
sion. Since the two multiplications have equal precedences, one is output before the J,
the second is placed on the stack and removed after the d is output. □

Token Stack Top Output


[0] [13 [2]
a -1 a
* * 0 a
( * ( 1 a
b * ( 1 ab
+ * ( + 2 ab
c * ( + 2 abc
) * 0 abc +
* * 0 abc +
*
d * 0 abc +^d
eos * 0 abc

Figure 3.16 : Translation of a^{h to postfix


Evaluation Of Expressions 125

The analysis of the two examples suggests a precedence-based scheme for stack­
ing and unstacking operators. The left parenthesis complicates matters because it
behaves like a low-precedence operator when it is on the stack, and a high-precedence
one when it is not. It is placed in the stack whenever it is found in the expression, but it
is unstacked only when its matching right parenthesis is found. Thus, we have two types
of precedence, an in-stack precedence {isp} and an incoming precedence (icp}. The
declarations that establish these precedences and the stack are:

precedence stack[MAX—STACK—SIZE];
/
* isp and icp arrays -- index is value of precedence
Iparen, rparen, plus, minus, times, divide, mod, eos
static int isp{] {0,19,12,12,13,13,13,0};
static int icp{} {20,19,12,12,13,13,13,0};

Notice that we are now using the stack to store the mnemonic for the token. Since
the value of a variable of an enumerated type is simply the integer corresponding to the
position of the value in the enumerated type, we can use the mnemonic as an index into
the two arrays. For example, isp[plus] is translated into /5p[2], which gives us an in­
stack precedence of 12. The precedences are taken from Figure 3.12, but we have added
precedences for the left and right parentheses and the eos marker. We give the right
parenthesis an in-stack and incoming precedence (19) that is greater than the precedence
of any operator in Figure 3.12. We give the left parenthesis an instack precedence of
zero, and an incoming precedence (20) greater than that of the right parenthesis. In addi­
tion, because we want unstacking to occur when we reach the end of the string, we give
the eos token a low precedence (0). These precedences suggest that we remove an
operator from the stack only if its instack precedence is greater than or equal to the
incoming precedence of the new operator.
The function postfix (Program 3.11) converts an infix expression into a postfix one
using the process just discussed. This function invokes a function, print-token, to print
out the character associated with the enumerated type. That is, print-token reverses the
process used in get-token.

Analysis of postfix: Let n be the number of tokens in the expression. 0(n) time is spent
extracting tokens and outputting them. Besides this, time is spent in the two while loops.
The total time spent here is 0{n) as the number of tokens that get stacked and unstacked
is linear in n. So, the complexity of function postfix is 0(«). □

EXERCISES

]. Write the postfix form of the following expressions:


126 Stacks And Queues

void postfix(void)
{
/
* output the postfix of the expression. The expression
string, the stack, and top are global */
char symbol;
precedence token;
int n 0;
int top = 0; *
/ place eos on stack
stack[0] eos ;
for (token = get—token(^symbol, &n); token != eos;
token = get—token (Scsymbol, &n) ) {
if (token == operand)
printf("%c", symbol) ;
else if (token == rparen) {
/
* unstack tokens until left parenthesis */
while (stack[top] != Iparen)
print—token(delete(&top));
delete (Sctop) ; / ★ discard the left parenthesis
/■^ ■^ !
}
else {
remove and print symbols whose isp is greater
than or equal to the current token's icp */
while(isp[stack[top]] = icp[token])
print—token(delete{&top));
add(&:top, token) ;
}

while ( (token=delete(&top)) != eos)


print—token(token);
printf("\n");
}

Program 3.11: Function to convert from infix to postfix

(a) a*b*c
(b) -a-^-b- c + d
(c) a —b c
(d) {a + b}^ d-^ e I c
Evaluation Of Expressions 127

(e) a 8l8l b II c ||! {e > f) (assuming C precedence)


(f) !(a && !((Z? < c) II (c > (7))) II ( c < e)
2. Write the print-token function used in postfix (Program 3.11).
3. Use the precedences of Figure 3.12 together with those for ’(’, ’)’, and \0 to answer
the following;
(a) In the postfix function, what is the maximum number of elements that can be
on the stack at any time if the input expression, expr, has n operators and an
unlimited number of nested parentheses?
(b) What is the answer to (a) if expr has n operators and the depth of the nesting
of parentheses is at most six?
4. Rewrite the eval function so that it evaluates the unary operators + and -.
5. § Rewrite the postfix function so that it works with the following operators,
besides those used in the text: &&, !!, «, », <=, 1= !=, <, >, <=, and >=. (Hint:
Write the equation so that the operators, operands, and parentheses are separated
with a space, for example, a + b > c. Then review the functions in <string.h>.)
6. Another expression form that is easy to evaluate and is parenthesis-free is known
as prefix. In prefix notation, the operators precede their operands. Figure 3.17
shows several infix expressions and their prefix equivalents. Notice that the order
of operands is the same in infix and prefix.

Infix Prefix
a^b/c /^abc
a/b-c +d^e-a^c -+-/abc^de^ac
{b +c)/d-g
*
a a +bcdg
—/*

Figure 3.17 : Infix and postfix expressions

(a) Write the prefix form of the expressions in Exercise 1.


(b) Write a C function that evaluates a prefix expression, expr. (Hint: Scan expr
from right to left.)
(c) Write a C function that transforms an infix expression, expr^ into its prefix
equivalent.

What is the time complexity of your functions for (b) and (c)? How much space is
needed by each of these functions?
7. Write a C function that transforms a prefix expression into a postfix one. Carefully
state any assumptions you make regarding the input. How much time and space
does your function take?
128 Stacks And Queues

8. Write a C function that transforms a postfix expression into a prefix one. How
much time and space does your function take?
9. Write a C function that transforms a postfix expression into a fully parenthesized
infix expression. A fully parenthesized expression is one in which all the subex­
pressions are surrounded by parentheses. For example, a +b +c becomes
((a +b)+c}. Analyze the time and space complexity of your function.
10. Write a C function that transforms a prefix expression into a fully parenthesized
infix expression. Analyze the time and space complexity of your function.
11. § Repeat Exercise 5, but this time transform the infix expression into prefix.

3.5 MULTIPLE STACKS AND QUEUES

Until now we have been concerned only with the representations of a single stack or a
single queue. In both cases, we have seen that it is possible to obtain efficient sequential
representations. We would now like to examine the case of multiple stacks. (We leave
the consideration of multiple queues as an exercise.) We again examine only sequential
mappings of stacks into an array, memory[MEMORY-SIZE]. If we have only two stacks
to represent, the solution is simple. We use memory [0] for the bottom element of the
first stack, and memory[MEMORY-SlZE - 7] for the bottom element of the second stack.
The first stack grows toward memory[MEMORY-SIZE - 7] and the second grows toward
memory [0]. With this representation, we can efficiently use all the available space.
Representing more than two stacks within the same array poses problems since we
no longer have an obvious point for the bottom element of each stack. Assuming that we
have n stacks, we can divide the available memory into n segments. This initial division
may be done in proportion to the expected sizes of the various stacks, if this is known.
Otherwise, we may divide the memory into equal segments.
Assume that stack-no refers to the stack number of one of the n stacks. To estab­
lish this stack, we must create indices for both the bottom and top positions of this stack.
The bottom element, boundary[stack-no], 0 < stack-no < MAX-STACKS, always points
to the position immediately to the left of the bottom element, while top[stack-no], 0 <
stack-no < MAX-STACKS points to the top element. A stack is empty iff
boundary[stack-no] = toplstack-no]. The relevant declarations are:

#define MEMORY-SIZE 100 size of memory */


/'*
#define MAX-STACKS 10 / :
* max number of stacks plus 1 */
/
* global memory declaration */
element memory[MEMORY—SIZE];
int top[MAX—STACKS] ;
int boundary[MAX—STACKS] ;
int n; *
/ number of stacks entered by the user *
/
To divide the array into roughly equal segments we use the following code:
Multiple Stacks And Queues 129

top[0] = boundary[0] -1;


for (i = 1; i < n;i++)
top[i] = boundary[i] (MEMORY_SIZE/n)
i;
*
boundary[n] = MEMORY_SIZE-1;

Figure 3.18 shows this initial configuration. In the figure, n is the number of stacks
entered by the user, n < MAX-STACKS, and m MEMORYSIZE. Stack stack-no can
grow from boundary[stack-no} + 1 to boundary [stack-no + 1] before it is full. Since
we need a boundary for the last stack, we set boundary [n ] to MEMORYSIZE- 1. Pro­
grams 3.12 and 3.13 implement the add and delete operations for this representation.

0 1 n/n 2 m/n m-1

t t t t
boundary[01 boundary[11 boundary[21 boundary[nl
top[01 toplll topC2]

All stacks are empty and divided into roughly equal segments.

Figure 3.18 : Initial configuration for n stacks in memory [m ].

void add (int i, element item)


{
add an item to the ith stack
if (top[i] == boundary[i+1])
stack_full(i) ;
memory[++top[i]] item;
}

Program 3.12: Add an item to the stack stack_no

The add (Program 3.12) and delete (Program 3.13) functions for multiple stacks
appear to be as simple as those we used for the representation of a single stack. How­
ever, this is not really the case because the = houndary[i+i} condition in add
implies only that a particular stack ran out of memory, not that the entire memory is full.
In fact, there may be a lot of unused space between other stacks in array memory (see
Figure 3.19). Therefore, we create an error recovery function, stackwhich deter­
mines if there is any free space in memory. If there is space available, it should shift the
130 Stacks And Queues

stacks so that space is allocated to the full stack.

element delete(int i)
{
*
/ remove top element from the ith stack */
if (top[i] == boundary[i])
return stack—empty{i);
return memory[top[i]—];
}

Program 3.13: Delete an item from the stack stack-no

t t T T T T t t t t
bCQ] tlOl bill till blil till tti+ll tlj] btj+ll bln]
bCi+ll bCi+Z:

b = boundary, t = top

Figure 3.19 : Configuration when stack i meets stack i + 1, but the memory is not full

There are several ways that we can design stack-full so that we can add elements
to this stack until the array is full. We outline one method here. Other methods are dis­
cussed in the exercises. We can guarantee that stack-full adds elements as long as there
is free space in array memory if we;
(1) Determine the least, j, stack-no < j < n, such that there is free space between
stacks j and j + 1. That is, top[j] < boundary[J+l]. If there is such a j, then move
stacks stack-no+li, stack-no+2, • • • , j one position to the right (treating
we/?R?n'[0] as leftmost and memory[MEMORY-SIZE - 7] as rightmost). This
creates a space between stacks stack-no and stack-no+i.
(2) If there is no j as in (1), then look to the left of stack stack-no. Find the largest j
such that 0 < 7 < stack-no and there is space between stacks j and 7+I. That is,
^op[J] < ^owAzJflzy'lj+l]. If there is such a j, then move stacks 7+I, j+2, • • ,
stack-no one space to the left. This also creates a space between stacks stack-no
and stack-no-^i.
(3) If there is no 7 satisfying either condition (1) or condition (2), then all
MEMORY-SIZE spaces of memory are utilized and there is no free space. In this
case stack -full terminates with an error message.
Multiple Stacks And Queues 131

We leave the implementation of stack-full as an exercise. However, it should be


clear that the worst case performance of this representation for the n stacks together will
be poor. In fact, in the worst case, the function has a time complexity of
O^MEMORYSIZE).

EXERCISES

1. We must represent two stacks in an array, memory[MEMORY-SlZE\. Write C


functions that add and delete an item from stack stack-no, 0 < stack-no < 1. Your
functions should be able to add elements to the stacks as long as the total number
of elements in both stacks is less than MEMORYSIZE - 1.
2. Obtain a data representation that maps a stack and a queue into a single array,
memory[MEMORYSIZE]. Write C functions that add and delete elements from
these two data objects. What can you say about the suitability of your data
representation?
3. Write a C function that implements the stack-full strategy discussed in the text.
4. Using the add and delete functions discussed in the text and stack-full from Exer­
cise 3, produce a sequence of additions/deletions that requires 0{MEM0RY-SIZE}
time for each add. Assume that you have two stacks and that your are starting
from a configuration representing a full utilization of memory[MEMORY-SlZE\.
5. Rewrite the add and stack-full functions so that the add function terminates if
there are fewer than cj free spaces left in memory. The empirically determined
constant, C| shows when it is futile to move items in memory. Substitute a small
constant of your choice.
6. Design a data representation that sequentially maps n queues into an array
memory[MEMORYSIZE]. Represent each queue as a circular queue within
memory. Write functions addq, deleteq, and queue-full for this representation.

3.6 REFERENCES AND SELECTED READINGS

You will find an excellent discussion of the system stack and activation records in A.
Holub, Compiler Design in C,Prentice-Hall, Englewood ClifTs, N.J., 1990. The structure
of our activation record (Figure 3.2) is based on Holub’s discussion.
Several texts discuss the precedence hierarchy used in C. Among the references
you might like to look at are S. Harbison and G. Steele, C: A Reference Manual, Third
Edition, Prentice-Hall, Englewood Cliffs, N.J., 1991, and B. Kernighan and D. Ritchie,
The C Programming Language, Second Edition, Prentice-Hall, Englewood Clifts, N.J.,
1988.
132 Stacks And Queues

3.7 ADDITIONAL EXERCISES

1. § [Programming project] [Landweber] People have spent so much time playing


solitaire that the gambling casinos are now capitalizing on this human weakness.
A form of solitaire is described below. You must write a C program that plays this
game, thus freeing hours of time for people to return to more useful endeavors.

To begin the game, 28 cards are dealt into seven piles. The leftmost pile has one
card, the next pile has two cards, and so forth, up to seven cards in the rightmost
pile. Only the uppermost card of each of the seven piles is turned face-up. The
cards are dealt left-to-right, one card to each pile, dealing one less pile each time,
and turning the first card in each round face-up. You may build descending
sequences of red on black or black on red from the top face-up card of each pile.
For example, you may place either the eight of diamonds or the eight of hearts on
the nine of spades or the nine of clubs. All face-up cards on a pile are moved as a
unit and may be placed on another pile according to the bottom face-up card. For
example, the seven of clubs on the eight of hearts may be moved as a unit onto the
nine of clubs or the nine of spades.

Whenever a face-down card is uncovered, it is turned face-up. If one pile is


removed completely, a face-up king may be moved from a pile (together with all
cards above it) or the top of the waste pile (see below) into the vacated space.
There are four output piles, one for each suite, and the object of the game is to get
as many cards as possible into the output piles. Each time an ace appears at the top
of a pile or the top of the stack it is moved into the appropriate output pile. Cards
are added to the output piles in sequence, the suit for each pile being determined
by the ace on the bottom.

From the rest of the deck, called the stock, cards are turned up one by one and
placed face-up on a waste pile. You may always play cards olTthe top of the waste
pile, but only one at a time. Begin by moving a card from the stock to the top of
the waste pile. If you can ever make more than one possible play, make them in
the following order:
(a) Move a card from the top of a playing pile or from the top of the waste pile
to an output pile. If the waste pile becomes empty, move a card from the
stock to the waste pile.
(b) Move a card from the top of the waste pile to the leftmost playing pile to
which it can be moved. If the waste pile becomes empty, move a card from
the stock to the waste pile.
(c) Find the leftmost playing pile that can be moved and place it on top of the
leftmost playing pile to which it can be moved.
Additional Exercises 133

(d) Try (a), (b), and (c) in sequence, restarting with (a) whenever a move is
made.
(e) If no move is made via (a) through (d), move a card from the stock to the
waste pile and retry (a).

Only the top card of the playing piles or the waste pile may be played to an output
pile. Once placed on an output pile, a card may not be withdrawn to help else­
where. The game is over when either all the cards have been played to the output
piles, or the stock pile has been exhausted and no more cards can be moved.

When played for money, the player pays the house $52 at the beginning, and wins
$5 for every card played to the output piles. Write your program so that it will
play several games and determine your net winnings. Use a random number gen­
erator to shuffle the deck. Output a complete record of two games in easily under­
standable form. Include as output the number of games played and the net win­
nings (-1- or -).
2. § [Programming project] [Landweber] We want to simulate an airport landing and
takeoff pattern. The airport has three runways, runway 0, runway 1, and runway 2.
There are four landing holding patterns, two for each of the first two runways.
Arriving planes enter one of the holding pattern queues, where the queues are to
be as close in size as possible. When a plane enters a holding queue, it is assigned
an integer identification number and an integer giving the number of time units the
plane can remain in the queue before it must land (because of low fuel level).
There is also a queue for takeoffs for each of the three runways. Planes arriving in
a takeoff queue are assigned an integer identification number. The takeoff queues
should be kept approximately the same size.

For each time period, no more than three planes may arrive at the landing queues
and no more than three planes may enter the takeoff queues. Each runway can
handle one takeoff or landing at each time slot. Runway 2 is used for takeoffs
except when a plane is low on fuel. During each time period, planes in either
landing queue whose air time has reached zero must be given priority over other
landings and takeoffs. If only one plane is in this category, runway 2 is used. If
there is more than one plane, then the other runways are also used.

Use successive even(odd) integers for identification numbers of the planes arriving
at takeoff (landing) queues. At each time unit assume that arriving planes are
entered into queues before takeoffs or landings occur. Try to design your algo­
rithm so that neither landing nor takeoff queues grow excessively. However, arriv­
ing planes must be placed at the ends of queues and the queues cannot be reor­
dered.

Your output should label clearly what occurs during each lime unit. Periodically
134 Stacks And Queues

you should also output:


(a) the contents of each queue
(b) the average takeoff waiting time
(c) the average landing waiting time
(d) the number of planes that have crashed (run out of fuel and there was no
open runway) since the last time period.

You might also like