CIS350 Data Structures & Advanced Programming


<< back to courseware demo page
- This online lecture is for demonstration purposes
 

Lecture III

Stacks

About This Lecture

This lecture introduces the stack data type and the last in, first out data access that it supports.  Contiguous (array) and linked implementations are given.

 

Before you begin

A review of C/C++ pointers and structs would be helpful in understanding the linked implementation.

Lecture Menu

About this Lecture

Learning Objectives

The LIFO nature of stacks

Reversing with a stack

Stack operations

Contiguous implementation

Linked implementation

Summary

Review Questions & Answers

Practice Test & Answers

Required Readings

Learning Objectives

 After completing Lecture III, you will understand aspects of the stack data type including:

  • Last in, first out (LIFO) data access
  • Push, pop and other stack operations
  • Contiguous implementation of a stack
  • Linked implementation of a stack
The LIFO Nature of Stacks
 
 
 
 

A stack is a data structure analogous to stacks encountered in everyday life.  For example, consider a stack of books on a desk.  One may easily put a new book on top of the stack, and one may easily remove the top book.  Adding books to, or removing them from, the middle of the stack may be perilous.  In the stack data structure accessing items in the middle is prohibited.  Items may be added to or removed from only the top of the stack.

Saving an item on a stack is referred to as pushing the item, and retrieving an item is called popping the item.  Popping an item removes the item from the stack.  Pushes and pops are done at the top of the stack, which may be thought of as a distinguished end of the stack.  This means that if two items are pushed and then one popped, the second one pushed will be popped.  This order of data access is called last in, first out or LIFO access.
 
 

Reversing with a Stack

 

Suppose a sequence of items is presented and it is desired to reverse the sequence.  Various methods could be used and the beginning programmer usually will suggest a solution using an array.  A conceptually very simple solution, however, is based on using a stack.  The LIFO property of stack access guarantees the reversal.

Suppose the sequence ABCDEF is to be reversed.  With a stack, one simply scans the sequence, pushing each item on the stack as it is encountered, until the end of the sequence is reached.  Then the stack is popped repeatedly, with each popped item sent to output, until the stack is empty.  The following illustrates this algorithm.

input ==> ABCDEF

push A:

A <== top of stack

input ==> BCDEF

push B:

B <== top of stack
A

input ==> CDEF

push C:

C <== top of stack
B

A

input ==> DEF

push D:

D <== top of stack
C

B

A

input ==> EF

push E:

E <== top of stack
D

C

B

A

input ==> F

push F:

F <== top of stack
E

D

C

B

A

The end of the input has been reached, and so popping the stack begins.

pop F to output:

E <== top of stack
D

C

B

A

pop E to output:

D <== top of stack
C

B

A

pop D to output:

C <== top of stack
B

A

pop C to output:

B <== top of stack
A

pop B to output:

A <== top of stack

pop A to output:

stack empty; stop.
 
 

Stack Operations

 

Besides the push and pop operations on a stack, it is desirable to have at least two more:  an initialize operation, which prepares the stack for use and sets it to an empty state, and an empty operation, which is simply a test to see whether the stack is empty.  The empty operation is useful to guard against an attempt to pop an empty stack, which is an error.  Ideally stacks should be unlimited in capacity so that another item can always be pushed no matter how many are already on the stack.  However, stacks as implemented on an actual computer will always have some finite maximum capacity.  Sometimes these stacks are referred to as stacks with a roof.  When the roof is reached, items can no longer be pushed on the stack.  In view of this fact, it is sometimes desirable to supply a full operation for a stack.

Other stack operations may be conceived, such as peek (examine the top item of the stack without popping it), traverse (go through all the items on the stack, performing some action for each item) and count (find the number of items on the stack).  Here, however, attention will be restricted to only the five mentioned in the preceding paragraph.

Stack Operations

  • push:  add an item to the top of the stack
  • pop:  remove an item from the top of the stack
  • empty: test whether the stack is empty
  • full:  test whether the stack is full
  • initialize: set up the stack in an empty condition


In C programs the above operations are often implemented as functions to provide a degree of data hiding.  A program which uses stacks would access the stacks only through these functions, and not be concerned with the inner workings of the stack.  It is also convenient to access a stack through a pointer.  The pointer can be considered a handle whereby the program can make use of the stack.  These techniques can provide implementation independent code, so that a program using stacks would not need to be changed if the stack functions themselves were changed.  This will be illustrated in the following two sections.

 

 

Contiguous Implementation

 

A contiguous implementation means that each stack item is adjacent in memory to the next stack item, and so arrays are the natural structures for contiguous implementations.  For a contiguous stack implementation the stack items are kept in an array and the top position in an integer field of a structure which also contains the array.  The stack will be accessed through a handle which is a pointer to this structure.  For flexibility the type itemtype will be used in the code for stack items.  Then the actual type used in a program can simply be specified with a typedef.  The constant N is the maximum size of the stack and may be redefined to a suitable value for different applications.

Reversing an Input Line with a Contiguous Stack

#include <stdio.h>
#include <stdlib.h>

#define N 80

typedef char itemtype;

typedef struct{
 int top;

 itemtype items[N];} stackstruct;

typedef stackstruct * stack;

stack initialize(stack s){
 s = (stack)malloc(sizeof(stackstruct));

 s->top = -1;

 return s;}

void push(stack s, itemtype x){
 s->items[++s->top] = x;}

itemtype pop(stack s){
 return s->items[s->top--];}

int empty(stack s){
 return s->top == -1;}

int full(stack s){
 return s->top == N-1;}

 

void main(){
 stack s;

 char c;

 s = initialize(s);

 while((c = getchar()) != '\n' && !full(s))

  push(s, c);

 while(!empty(s))

  putchar(pop(s));

 getchar();}

 

Input

palindromes

Output

semordnilap
 
 

Linked Implementation

 

A stack can also be implemented as a linked structure.  In such an implementation the stack consists of a sequence of nodes.  Each node is a record (structure in C) containing a data item and a pointer to the next node if one exists.  This pointer is called a link (to the next node).  The first node is considered to be the top of the stack, and will be pointed to by a pointer called top.  The last node is the bottom of the stack and its pointer field is set to NULL.  An empty stack will have top == NULL.  A linked stack with elements C, B, A in order (C on top) may be represented as below, where \ denotes a NULL pointer.

                    __________              __________              _________
top ======> |__C__ |___| =====> |__B__ |___| =====> |__A__ |_\_|

Recall that it is desirable for a program to access a stack through a handle.  To achieve this, we simply enclose the top field in a structure, and then a pointer to this structure is the desired handle.  It may seem strange to have a structure with only one field, but this ensures that a program can use a linked stack in exactly the same way it would use a contiguous stack.

The memory for each node is dynamically  allocated on the heap using malloc.  So when an item is pushed, a node for it is created, and when an item is popped, its node is freed (using free).

The following program illustrates appropriate types and functions for a linked stack.  The main function below is identical to the main function above using a contiguous stack.  Note that the behavior of this program is the same as the one for a contiguous stack, at least for input lines of 80 characters or less.  This is because the same function names are used for the linked stack operations and they provide the same functionality for the linked stack as the previous ones do for the contiguous stack.  The only difference is that the capacity of a linked stack is generally greater than a contiguous stack since a linked stack will not become full until dynamic memory is exhausted.

Reversing an Input Line with a Linked Stack

#include <stdio.h>
#include <stdlib.h>

typedef char itemtype;

typedef struct node{
 itemtype item;

 struct node * next;} Node;

typedef struct {Node * top;} stackstruct;

typedef stackstruct * stack;

void push(stack s, itemtype x){
 Node * p = (Node*)malloc(sizeof(Node));

 p->item = x;

 p->next = s->top;

 s->top = p;}

itemtype pop(stack s){
 Node * p = s->top;

 itemtype x = p->item;

 s->top = p->next;

 free(p);

 return x;}

int empty(stack s){
 return s->top==NULL;}

int full(stack s){
 Node * p = (Node*)malloc(sizeof(Node));

 if(p == NULL) return 1;

 else{ free(p);

  return 0;}}

 

stack initialize(stack s){
 s = (stack)malloc(sizeof(stackstruct));

 s->top = NULL;

 return s;}

void main(){
 stack s;

 char c;

 s = initialize(s);

 while((c = getchar()) != '\n' && !full(s))

  push(s, c);

 while(!empty(s))

  putchar(pop(s));

 getchar();}

 

Summary
 

This lecture has covered the concept of the stack data type and its LIFO data access.
The stack operations below were discussed and contiguous and linked stack implementations presented.

Stack Operations

  • push:  add an item to the top of the stack
  • pop:  remove an item from the top of the stack
  • empty: test whether the stack is empty
  • full:  test whether the stack is full
  • initialize: set up the stack in an empty condition
Review Questions
 

1.  What are some common stack operations?

2.  What is meant by the LIFO nature of stacks?

3.  What does the push operation do to a stack?

4.  What does the pop operation do to a stack?

5.  What is meant by the top of a stack?
 

 

Practice Test & Answers
 

1.  If the characters of a line of text are pushed on a stack in order from left to right, and then the stack is popped until empty, what does the sequence of popped characters represent?

2.  What is the difference between a linked stack and a contiguous stack?

3.  In the following sequence a letter means push that letter on the stack and * means pop a character from the stack.  Indicate the character resulting from each pop.
LAST***IN***FIRST***OUT***STACK***

4.  Suppose the characters of a line of text are pushed on a stack in order from left to right, and then the stack is popped until empty with the popped characters being pushed, as they are popped, onto a second stack.  The second stack is then popped until empty.  What does the second sequence of popped characters represent?

5.  Access to data in a stack occurs at the ___ of the stack.
 

ANSWERS

1.  the line of text in reverse order

2.  The data items in a contiguous stack are elements in an array and a top index needs to be maintained.  The data items in a linked stack are fields in the nodes of the linked structure and may be far apart in memory.  A linked stack generally has a greater capacity than a contiguous stack.

3.  TSANILTSRTUOKCA

4.  the text in its original order

5.  top
 

 

Required Readings

Read sections 18.1 and 18.2 in Gaddis.