Understanding Stacks in Data Structures
Understanding Stacks in Data Structures
Module 2:
STACKS:
1. Definition
A Stack is a linear data structure that follows a particular order in which the operations are
performed. The order may be LIFO(Last In First Out) or FILO(First In Last Out). LIFO implies
that the element that is inserted last, comes out first and FILO implies that the element that is
inserted first, comes out last.
As shown in above figure, the elements are added in the stack in the order A, B, C, D, E, then E
is the first element that is deleted from the stack and the last element is deleted from stack is
A. Figure 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-First-Out (LIFO) list.
SYSTEM STACK
2. Stack Operations
• push(): When we insert an element in a stack then the operation is known as a push. If the stack
a) PUSH operation
The steps involved in the PUSH operation are given below:
o Before inserting an element in a stack, we check whether the stack is full.
o If we try to insert the element in a stack, and the stack is full, then
the overflow condition occurs.
o When we initialize a stack, we set the value of top as -1 to check that the stack is empty.
o When the new element is pushed in a stack, first, the value of the top gets incremented,
i.e., top=top+1, and the element will be placed at the new position of the top.
o The elements will be inserted until we reach the max size of the stack.
b) POP operation
The steps involved in the POP operation are given below:
o Before deleting the element from the stack, we check whether the stack is empty.
o If we try to delete the element from the empty stack, then the underflow condition
occurs.
o If the stack is not empty, we first access the element which is pointed by the top
o Once the pop operation is performed, the top is decremented by 1, i.e., top=top-1.
Stack ADT
• The following operations make a stack an ADT. For simplicity, assume the
data is an integertype.
I. Create stack:
• Function push() checks to see if the stack is full. If it is, it calls stackFull, which
prints anerror message and terminates execution.
• When the stack is not full, we increment top and assign item to stack[top].
For deletion, the stack-empty function should print an error message and return an
item oftype element with a key field that contains an error code.
ANALYSIS
5. Stack applications:
Polish notation
Expressions: It is sequence of operators and operands that reduces to a single
value afterevaluation is called an expression.
X=a/b–c+d*e– a*c
In above expression contains operators (+, –, /, *) operands (a, b, c, d, e).
Expression can be represented in in different format such as
• Prefix Expression or Polish notation
• Infix Expression
• Postfix Expression or Reverse Polish notation
• Infix Expression: In this expression, the binary operator is placed in-
between the operand. The expression can be parenthesized or un-
parenthesized.
Example: A + B
Here, A & B are operands and + is operand
• Prefix or Polish Expression: In this expression, the operator appears before
itsoperand.
Example: + A B
Here, A & B are operands and + is operand
• Postfix or Reverse Polish Expression: In this expression, the operator
appearsafter its operand.
Example: A B +
Here, A & B are operands and + is operand
Precedence of the operators
The analysis of the examples suggests a precedence-based scheme for stacking 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.
• There are two types of precedence, in-stack precedence (isp) and incoming
precedence (icp).
The declarations that establish the precedence’s are:
/* isp and icp arrays-index is value of precedence lparen rparen, plus,
minus, times,divide, mod, eos */
int isp[] = {0,19,12,12,13,13,13,0};
int icp[] = {20,19,12,12,13,13,13,0};
Example: Consider the infix expression exp = “a+b*c+d” and the infix expression is scanned
using the iterator i, which is initialized as i = 0.
1st Step: Here i = 0 and exp[i] = ‘a’ i.e., an operand. So add this in the postfix expression.
Therefore, postfix = “a”.
2nd Step: Here i = 1 and exp[i] = ‘+’ i.e., an operator. Push this into the stack. postfix = “a” and
stack = {+}.
3rd Step: Now i = 2 and exp[i] = ‘b’ i.e., an operand. So add this in the postfix expression.
postfix = “ab” and stack = {+}.
4th Step: Now i = 3 and exp[i] = ‘*’ i.e., an operator. Push this into the stack. postfix = “ab”
and stack = {+, *}.
5th Step: Now i = 4 and exp[i] = ‘c’ i.e., an operand. Add this in the postfix expression. postfix
= “abc” and stack = {+, *}.
6th Step: Now i = 5 and exp[i] = ‘+’ i.e., an operator. The topmost element of the stack has
higher precedence. So pop until the stack becomes empty or the top element has less
precedence. ‘*’ is popped and added in postfix. So postfix = “abc*” and stack = {+}.
Now top element is ‘+‘ that also doesn’t have less precedence. Pop it. postfix = “abc*+”.
7th Step: Now i = 6 and exp[i] = ‘d’ i.e., an operand. Add this in the postfix expression. postfix
= “abc*+d”.
Final Step: Now no element is left. So empty the stack and add it in the postfix expression.
postfix = “abc*+d+”.
Postfix expression: The expression of the form “a b operator” (ab+) i.e., when a pair of
operands is followed by an operator.
Examples:
Input: str = “2 3 1 * + 9 -“
Output: -4
Explanation: If the expression is converted into an infix expression,
it will be 2 + (3 * 1) – 9 = 5 – 9 = -4.
Input: str = “100 200 + 2 / 5 * 7 +”
Output: 757
Evaluation of Postfix Expression using Stack:
To evaluate a postfix expression we can use a stack.
Iterate the expression from left to right and keep on storing the operands into a stack. Once
an operator is received, pop the two topmost elements and evaluate them and push the result
in the stack again.
Illustration:
Follow the below illustration for a better understanding:
Consider the expression: exp = “2 3 1 * + 9 -“
• Scan 2, it’s a number, So push it into stack. Stack contains ‘2’.
• Scan 3, again a number, push it to stack, stack now contains ‘2 3’ (from bottom to top)
• Scan +, it’s an operator. Pop two operands from stack, apply the + operator on operands.
We get 3 + 2 which results in 5. We push the result 5 to stack. The stack now becomes ‘5’.
• There are no more elements to scan, we return the top element from the stack (which is
the only element left in a stack).
So the result becomes -4.
Follow the steps mentioned below to evaluate postfix expression using stack:
• Create a stack to store operands (or values).
• Scan the given expression from left to right and do the following for every scanned
element.
o If the element is a number, push it into the stack.
o If the element is an operator, pop operands for the operator from the stack.
Evaluate the operator and push the result back to the stack.
• When the expression is ended, the number in the stack is the final answer.
Below is the implementation of the above approach:
// C program to evaluate value of a postfix expression
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Stack type
struct Stack
{
int top;
unsigned capacity;
int* array;
};
// Stack Operations
struct Stack* createStack(unsigned capacity)
{
struct Stack* stack
= (struct Stack*)malloc(sizeof(struct Stack));
if (!stack)
return NULL;
stack->top = -1;
stack->capacity = capacity;
stack->array
= (int*)malloc(stack->capacity * sizeof(int));
if (!stack->array)
return NULL;
return stack;
}
int isEmpty(struct Stack* stack)
{
return stack->top == -1;
}
char peek(struct Stack* stack)
{
return stack->array[stack->top];
}
char pop(struct Stack* stack)
{
if (!isEmpty(stack))
return stack->array[stack->top--];
return '$';
}
void push(struct Stack* stack, char op)
{
stack->array[++stack->top] = op;
}
// The main function that returns value
// of a given postfix expression
int evaluatePostfix(char* exp)
{
// Create a stack of capacity equal to expression size
struct Stack* stack = createStack(strlen(exp));
int i;
// See if stack was created successfully
if (!stack)
return -1;
// Scan all characters one by one
for (i = 0; exp[i]; ++i) {
// If the scanned character is an operand
// (number here), push it to the stack.
if (isdigit(exp[i]))
push(stack, exp[i] - '0');
// If the scanned character is an operator,
// pop two elements from stack apply the operator
else
{
int val1 = pop(stack);
int val2 = pop(stack);
switch (exp[i])
{
case '+':push(stack, val2 + val1);
break;
case '-': push(stack, val2 - val1);
break;
case '*': push(stack, val2 * val1);
break;
case '/': push(stack, val2 / val1);
break;
}
}
}
return pop(stack);
}
// Driver code
int main()
{
char exp[] = "231*+9-";
// Function call
printf("postfix evaluation: %d", evaluatePostfix(exp));
return 0;
}
Output
postfix evaluation: -4
Chapter 2:
1. Queues:
Queue is the data structure that is similar to the queue in the real world. A queue is a data
structure in which whatever comes first will go out first, and it follows the FIFO (First-In-First-
Out) policy. Queue can also be defined as the list or collection in which the insertion is done
from one end known as the rear end or the tail of the queue, whereas the deletion is done
from another end known as the front end or the head of the queue.
The real-world example of a queue is the ticket queue outside a cinema hall, where the person
who enters first in the queue gets the ticket first, and the last person enters in the queue gets
the ticket at last. Similar approach is followed in the queue in data structure.
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 = (a0, a1, • • • ,an-1),a0 is the front element, an-1 is
the rear element, and ai+1 is behind ai,0<= i < n-1. 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. Fig 2
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.
We can easily represent queue by using linear arrays. There are two variables i.e. front and
rear, that are implemented in the case of every queue. Front and rear variables point to the
position from where insertions and deletions are performed in a queue. Initially, the value of
front and queue is -1 which represents an empty queue. Array representation of a queue
containing 5 elements along with the respective values of front and rear, is shown in the
following figure.
The above figure shows the queue of characters forming the English word "HELLO". Since, No
deletion is performed in the queue till now, therefore the value of front remains -1 . However,
the value of rear increases by one every time an insertion is performed in the queue. After
inserting an element into the queue shown in the above figure, the queue will look something
like following. The value of rear will become 5 while the value of front remains same.
After deleting an element, the value of front will increase from -1 to 0. however, the queue will
look something like following.
3. Queue Operations
The representation of a queue in sequential locations is more difficult than that of the tack. The
simplest scheme employs a one-dimensional array and two variables, front and rear. Given this
representation, we can define the queue operations in Structure as
Types of Queue
There are four different types of queue that are listed as follows -
In Linear Queue, an insertion takes place from one end while the deletion occurs from another
end. The end at which the insertion takes place is known as the rear end, and the end at which
the deletion takes place is known as front end. It strictly follows the FIFO rule.
The major drawback of using a linear Queue is that insertion is done only from the rear end. If
the first three elements are deleted from the Queue, we cannot insert more elements even
though the space is available in a Linear Queue. In this case, the linear Queue shows the
overflow condition as the rear is pointing to the last element of the Queue.
Isfull(): Before inserting an item into the queue, we need to check whether the queue is full or
not. If rear is the same as QUEESIZE-1, then queue isfull. Isfull() function can be implemented
as:
Int Isfull(int r)
{
return r==QUEUESIZE-1; //returns1, if the queue is full, else returns 0 if the queue is not full.
}
InsertQ(): To insert the item, rear has to be increment by 1 and then item can be
inserted at rear end of queue. Before inserting, we check queue is full or not using Isfull
() function. Hence, InsertQ () function can be implemented as:
void insertQ(int item,int *r,int *q)
{
if(Isfull(*r)
{
printf(“queueoverflow\n”);
return;
}
q[++(*r)]=item;//rear is incrementing first and then item is inserted at rear
}
Isempty():Whenever the value of front is greater than that of rear, then queue is empty.
Isempty () function can be implemented as:
intIsempty(int f,int r)
{
return f>r;//returns1,if queue is empty, else returns0.
}
deleteQ ();
In a queue, element is always deleted from the front end. Before deleting, check whether
queue is empty. This can be done using Isempty () function. To delete an item,we have to
access the item at front and then increment front value by [Link] deleteQ () can be
implemented as:
void deleteQ(int *q,int *f,int *r)
{
if(Isempty(*f,*r))
{
printf(“Queue Empty\n”);
return;
}
printf(“itemdeletedis%d\n”,q[(*f)++]);
}
Display():
If the queue already has some elements, all those elements are displayed one after the other.
If no elements are present, appropriate error message can be displayed. Hence displayQ ()
function can be implemented as:
{
printf(“QueueEmpty\n”); return;
}
for (i=f; i<=r; i++)
printf(“%d\n”,q[i]);
}
voidmain()
{
int choice, item, f, r, q [10];
f=0; // initialize front end as 0
r=-1; // initialize rear end as -1
for (; ;)
{
printf("\[Link]");
printf ("\n enter choice");
scanf("%d”,&choice);
switch(choice)
{
case1: printf("\n enter item to be inserted");
scanf ("%d", &item);
insertQ(item, &r, q);
break;
case2: deleteQ(q,&f,&r);
break;
case3: displayQ(q,f,r);
break;
default: exit(0);
}
}
}
Disadvantage of queue: Consider the queue as shown below:
In the above situation, rear insertion is denied even if space is available at the front end.
This is because, before inserting an element, it checks whether the rear is
[Link] so, the Queue is full and the element can’t be inserted. This is a
disadvantage. This disadvantage can be overcome by using a circular queue.
5. Circular Queue: The elements of a given queue can be stored in an array so as to “wrap
around” so that end of the queue is followed by the front of queue.
The pictorial representation of a circular queue is as shown below:
InsertcircularQ (): While inserting elements into the circular queue, the various steps
have to be followed as shown below:
i) Check for Overflow: Before inserting elements, check whether sufficient space is available in
the queue.
ii) Insert item: Increment rear by 1 and then take mod operation and insert item.
iii) Update count: After inserting item, increment count by 1. Count contains the total number
of elements present in the queue.
The complete insertcircularQ () function is as follows:
void insertcircularQ (int item, int *rear, int *count, int *q)
{
if (*count ==QUEUESIZE)
{
printf (“Queue Full\n”);
return;
}
*rear =(*rear+1) %QUEUESIZE;
q[*rear] = item;
(*count) ++;
}
DeletecircularQ (): While deleting elements from the circular queue, the various steps have
to be followed as shown below:
i) Check for Underflow: Before deleting elements, check whether the queue is empty.
ii) Access item: Access the element by using the front index and then increment the front by 1
and then take mod operation.
iii) Update count: After deleting the item, decrement the count by 1.
iv) Return the item: return the element which was at the index front.
DisplaycircularQ (): this function first check for a queue empty, if not it will display the
contents of the circular queue starting from the index front. The procedure is repeated for
‘count’ number of times. Here, count gives the total number of elements in the queue. If queue
is empty, it displays the error message as queue empty.
(*c) --;
return x;
}
void display (int *q, int f, int c)
{
int i, j;
if(c==0)
{
printf ("\nQueue is empty");
return;
}
printf ("contents of queue is\n");
for (i=1, j=f; i<=c; i++)
{
printf ("\n%d", q[j]);
j=(j+1) %MAXSIZE;
}
}
void main ()
{
int choice;
int *q, r, f, c, item;
r=-1;
f=0;
c=0;
for (; ;)
{
printf ("\nenter choice");
scanf("%d",&choice);
switch(choice)
{
case 1: printf ("\nenter the item");
scanf ("%d”, &item);
insert (item, q, &f, &r, &c);
break;
case 2: item=delete(q,&f,&c);
if(item==-1)
printf ("\nQueue Empty");
else
printf ("\nitem deleted = %d”, item);
break;
case 3: display(q,f,c);
break;
default: exit (0);
}
}
}
Circular Queue using Dynamic arrays: If static arrays are used to implement queue, then
the maximum size of the queue should be known during compilation. Since queue size is fixed
during compilation, it is not possible to alter the queue size during execution. This disadvantage
can be overcome using dynamic arrays.
C program to implement circular queue using dynamic arrays
#include <stdio.h>
#include<stdlib.h>
int MAXSIZE=1;
#define MALLOC(p,s)\
if (! (p=malloc(s))) \
{\
printf ("\n memory insufficient"); \
exit(0); \
}
#define REALLOC(p,s) \
if(!(p=realloc(p,s))) \
{\
printf("\n memory insufficient");\
exit(0);\
}
void insert(int item,int *q, int *f, int *r, int *c)
{
int i ;
if(*c==MAXSIZE)
{
printf ("\n QFull: increase size ");
MAXSIZE++;
REALLOC (q, MAXSIZE*sizeof(int));
}
if(*f>*r && *r! =-1)
{
6. Priority Queue:
It is a special type of queue in which the elements are arranged based on the priority. It is a
special type of queue data structure in which every element has a priority associated with it.
Suppose some elements occur with the same priority, they will be arranged according to the
FIFO principle. The representation of priority queue is shown in the below image -
Insertion in priority queue takes place based on the arrival, while deletion in the priority queue
occurs based on the priority. Priority queue is mainly used to implement the CPU scheduling
algorithms.
There are two types of priority queue that are discussed as follows -
Deque can be used both as stack and queue as it allows the insertion and deletion operations
on both ends. Deque can be considered as stack because stack follows the LIFO (Last In First
Out) principle in which insertion and deletion both can be performed only from one end. And in
deque, it is possible to perform both insertion and deletion from one end, and Deque does not
follow the FIFO principle.
o Input restricted deque - As the name implies, in input restricted queue, insertion
operation can be performed at only one end, while deletion can be performed from
both ends.
o Output restricted deque - As the name implies, in output restricted queue, deletion
operation can be performed at only one end, while insertion can be performed from
both ends.
8. 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 position r 1][1 ] and the exit at [w][p].
Another device that will simplify the problem is to predefine the possible directions 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 offset.
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:
Although this algorithm describes the essential processing, we must still resolve several issues.
Our first concern is with the representation of the stack. Examining Program 3.7, we see that
the stack functions created in Section 3.2 will work if we redefine element as:
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.