Module 2 Notes
Module 2 Notes
MODULE-2
STACKS AND QUEUES
TOPICS
Stacks: Definition, Stack Operations, Array Representation of Stacks, Stacks using
Dynamic Arrays, Stack Applications: Polish notation, Infix to postfix conversion,
evaluation of postfix expression.
Recursion: Factorial, GCD, Fibonacci Sequence, Tower of Hanoi, Ackerman's function.
Queues: Definition, Array Representation, Queue Operations, Circular Queues, Circular
queues using Dynamic arrays, Dequeues, Priority Queues, A Mazing Problem. Multiple
Stacks and Queues. Programming Examples.
STACK
“Stack is an ordered collection of elements or items of same type can be inserted and deleted at only
one end called Top of stack”. (OR)
STACK is an ordered-list in which insertions (called push) and deletions (called pop) are
made at one end called the top. Since last element inserted into a stack is first element removed, a
stack is also known as a LIFO list (Last In First Out). Stack can be implemented using the Linked List
or Array.
Stack belongs to non-primitive linear data structure.
Consider a stack s=(a0,a1,….,an-1).here a0 is the bottom element , an-1 is the top element and
generally, element ai is on the top of element ai-1,0<i<n.
Top an-1
an-2
…..
….
a1
a0
Figure 1a: Stack S
If A, B, C, D, E are the elements added into stack in that order then E is the first element to be
deleted. Figure shows insertion and deletion of elements from the stack.
• If one function invokes another function, local variables and parameters of the invoking-function are
added to its stack-frame.
• A new stack-frame is then created for the invoked-function & placed on top of the system-stack.
• When this function terminates, its stack-frame is removed (and processing of the invoking-function,
which is again on top of the stack, continues).
• Frame-pointer(fp) is a pointer to the current stack-frame.
POP Operation
Accessing the content while removing it from stack, is known as pop operation. In array
implementation of pop() operation, data element is not actually removed, instead top is decremented to a
lower position in stack to point to next value. But in linked-list implementation, pop() actually removes
data element and deallocates memory space. Pop operation is shown in figure6.
A POP operation may involve the following steps −
● Step 1 − Check if stack is empty.
● Step 2 − If stack is empty, produce error and exit.
● Step 3 − If stack is not empty, access the data element at which top is pointing.
● Step 4 − Decrease the value of top by 1.
● Step 5 − return success.
Function push/add() checks to see if the stack is full. If it is, it calls stack_full(), which prints an
error message and terminates execution. When the stack is not full, we increment top and assign item to
stack[top].
Function pop/delete() checks to see if the stack is empty using top . If top reaches -1,then it
calls stack_empty(), which prints an error message and terminates execution. When the stack is not
empty, we return the top most element stack[top] and decrement top.
void stack_full()
{ printf(stderr,”stack is full, can’t add element”);
exit(EXIT_FAILURE);
}
void stack_empty ()
{ printf(stderr,”stack is empty, can’t delete element”);
exit(EXIT_FAILURE);
}
void stack_full()
{ printf(stderr,”stack is full, can’t add element”);
exit(EXIT_FAILURE);
}
void stack_empty ()
{ printf(stderr,”stack is empty, can’t delete element”);
exit(EXIT_FAILURE);
}
Once the stack is full, realloc() function is used to increase the size of array.In array-doubling,
we double array-capacity whenever it becomes necessary to increase the capacity of an array.
ANALYSIS
In worst case, the realloc function needs to allocate 2*capacity*sizeof(*stack) bytes of memory
and copy capacity*sizeof(*stack) bytes of memory from the old array into the new one. The total time
spent over all array doublings = O(2k) where capacity=2k. Since the total number of pushes is more than
2k-1, the total time spend in array doubling is O(n) where n=total number of pushes.
● While evaluating the postfix expression the precedence and Associativity of the operators is no
longer required
● all expressions given to the system , will be converted into postfix form by the complier since it
is easy and more efficient to evaluate.
Converting expressions using Stack:
Let us convert the expressions from one type to another. These can be done as follows
1. Infix to postfix
2. Infix to prefix
3. Postfix to infix
4. Postfix to prefix
5. Prefix to infix
6. Prefix to postfix
We consider six binary arithmetic operations: +, -, *, / and % or ^ (power). For these binary operations,
the following in the order of precedence (highest to lowest):
Operators precedence
^ 4
* / % 3
+ - 2
# ( 1
Example 1:
Convert ((A – (B + C)) * D) ↑ (E + F) infix expression to postfix form:
Example 2:
Convert the following infix expression A + B * C – D / E * H into its equivalent postfix expression
Example 1:
Evaluate the postfix expression: 6 5 2 3 + 8 * + 3 + *
2.5 RECURSION
Recursion is the process of repeating items in a self-similar way. In programming languages, if a
program allows you to call a function inside the same function, then it is called a recursive call of the
function.
void recursion()
{
recursion(); /* function calls itself */
}
void main()
{
recursion();
}
The C programming language supports recursion, i.e., a function to call itself.
A recursive function is a function that calls itself during its execution.
But while using recursion, programmers need to be careful to define an exit condition from the function;
otherwise it will go into an infinite loop.
Recursive functions are very useful to solve many mathematical problems, such as calculating the
factorial of a number, generating Fibonacci series, etc.
RECURSION PROPERTIES
A recursive function can go infinite like a loop. To avoid infinite running of recursive function,
there are two properties that a recursive function must have −
● Base criteria − There must be at least one base criteria or condition, such that, when this
condition is met the function stops calling itself recursively.
● Recursive criteria − the recursive calls should progress in such a way that each time a recursive
call is made it comes closer to the base criteria.
1. FACTORIAL NUMBER
The product of the positive integers from 1 to n, inclusive is called n factorial and is usually
denoted by n!
n!=1*2*3*4*………*(n-2)*(n-1)*n.
Factorial function may be defined as
a. if n=0 then return 1
b. if n>0, then return n*(n-1)!
The following example calculates the factorial of a given number using a recursive function
#include <stdio.h>
int factorial(unsigned int n)
{
if(n <= 1)
{
return 1;
}
return n * factorial(n - 1);
}
void main()
{
int n,res;
printf(“\n enter the value for n”);
scanf(“%d”,&n);
res=factorial(n);
printf("Factorial of %d is %d\n", n, res);
}
When the above code is compiled and executed, it produces the following result − Factorial of 15 is
2004310016
Euclid’s algorithm
1. Take a and b, and calculate the remainder by performing a%b.
2. Assign the value of b to a and value of remainder to b.
3. Repeat the steps 1 & 2 until value of b becomes 0.
4. If b=0, then return value of ‘a’ as the GCD value of a & b.
#include <stdio.h>
int gcd(int m, int n);
void main()
{
int a, b,res;
printf("Enter two positive integers: ");
scanf("%d %d", &a, &b);
res=gcd(a,b);
printf("G.C.D of %d and %d is %d.", n1, n2, res);
}
int gcd(int a, int b)
{
int rem;
if (b==0)
return a;
else
{
rem=a%b;
a=b;
b=rem;
return gcd(a,b);
}
Output
Enter two positive integers:
366 60
G.C.D of 366 and 60 is 6
3. FIBONACCI SERIES
The Fibonacci sequence is the sequence of integers, where each number in this sequence is the
sum of two preceding elements.
A formal definition is
a. If n=0 or 1,return n(0/1)
b. If n>1,return fib(n-1)+fib(n-2)
#include <stdio.h>
int fib(int i)
{
if(i == 0)
{
return 0;
}
if(i == 1)
{
return 1;
}
return (fib(i-1) + fib(i-2));
}
void main()
{
int i;
for (i = 0; i < 10; i++)
{
printf("%d\t", fib(i));
}
}
When the above code is compiled and executed, it produces the following result −
0 1 1 2 3 5 8 13 21 34
Here is an animated representation in figure 8 of solving a tower of hanoi puzzle with three disks
−Tower of hanoi puzzle with n disks can be solved in minimum 2n−1 steps. This presentation shows
that a puzzle with 3 disks has taken 23−1 = 7 steps.
Algorithm
To write an algorithm for Tower of Hanoi, first we need to learn how to solve this problem with lesser
amount of disks, say → 1 or 2. We mark three towers with name, source, destination and aux (only to
help moving disks). If we have only one disk, then it can easily be moved from source to destination
peg.
If we have 2 disks −
● First we move the smaller one (top) disk to aux peg
● Then we move the larger one (bottom) disk to destination peg
● And finally, we move the smaller one from aux to destination peg.
So now we are in a position to design algorithm for Tower of Hanoi with more than two disks. We
divide the stack of disks in two parts. The largest disk (nthdisk) is in one part and all other (n-1) disks
are in second part. Our ultimate aim is to move disk n from source to destination and then put all other
(n-1) disks onto it. Now we can imagine to apply the same in recursive way for all given set of disks.
So steps to follow are −
● Step 1 − Move n-1 disks from source to aux
● Step 2 − Move nth disk from source to dest
● Step 3 − Move n-1 disks from aux to dest
A recursive algorithm for Tower of Hanoi can be driven as follows −
START
Procedure tower(disk, source, dest, aux)
if n == 1, THEN
move disk from source to dest
else
tower(n - 1, source, aux, dest) // Step 1
move disk from source to dest // Step 2
tower(n - 1, aux, dest, source) // Step 3
END IF
END Procedure
STOP
PROGRAM:
#include<stdio.h>
#include<conio.h>
#include <stdio.h>
void towers(int, char, char, char);
int main()
{
int num;
printf("Enter the number of disks : ");
scanf("%d", &num);
printf("The sequence of moves involved in the Tower of Hanoi are :\n");
towers(num, 'A', 'C', 'B');
getch();
return 0;
}
void towers(int num, char source, char dest, char aux)
{
if (num == 1)
{
printf("\n Move disk 1 from peg %c to peg %c", source,dest);
return;
}
towers(num - 1, source, aux,dest);
printf("\n Move disk %d from peg %c to peg %c", num, source, dest);
towers(num - 1, aux, dest, source);
}
The below figure shows the disks movements in tower of Hanoi for 3 disks
ACKERMANN'S FUNCTION
2.6 QUEUES
Queue is an ordered-list in which insertions & deletions take place at different ends. The end at
which new elements are added is called the rear &the end from which old elements are deleted is called
the front. Since first element inserted into a queue is first element removed, queues are known as FIFO
lists.
(OR)
Queue is an abstract data structure, somewhat similar to Stack. In contrast to Queue, queue is opened at
both ends. One end is always used to insert data (enqueue) and the other is used to remove data
(dequeue). Queue follows First-In-First-Out methodology, i.e., the data item stored first will be accessed
first.(as shown in figure 9)
ADT:-QUEUE
APPLICATIONS OF QUEUE
Queue, as the name suggests is used whenever we need to have any group of objects in an order in
which the first one coming in, also gets out first while the others wait for there turn, like in the following
scenarios :
1. Serving requests on a single shared resource, like a printer, CPU task scheduling etc.
2. In real life, Call Center phone systems will use Queues, to hold people calling them in an order, until
a service representative is free.
3. Handling of interrupts in real-time systems. The interrupts are handled in the same order as they
arrive, First come first served
2.7 QUEUE BASIC OPERATIONS
Queue operations may involve initializing or defining the queue, utilizing it and then completing erasing
it from memory. Here we shall try to understand basic operations associated with queues −
● enqueue() − add (store) an item to the queue.
Few more functions are required to make above mentioned queue operation efficient. These are −
● peek() − get the element at front of the queue without removing it.
In queue, we always dequeue (or access) data, pointed by front pointer and while enqueing (or storing)
data in queue we take help of rear pointer.
peek() Like Queues, this function helps to see the data at the front of the queue. Code for the peek()
function −
int peek()
{
return queue[front];
}
isfull()
As we are using single dimension array to implement queue, we just check for the rear pointer to reach
at MAXSIZE to determine that queue is full. In case we maintain queue in a circular linked-list, the
algorithm will differ. Implementation of isfull() function in C programming language −
bool isfull()
{
if(rear == MAXSIZE - 1)
return true;
else
return false;
}
isempty()
Here's the C programming code −
bool isempty()
{
if(front < 0 || front > rear)
return true;
else
return false;
}
Enqueue (Insert) Operation
As queue maintains two data pointers, front and rear, its operations are comparatively more difficult to
implement than Queue.(as shown in figure 12)
The following steps should be taken to enqueue (insert) data into a queue −
● Step 1 − Check if queue is full.
● Step 2 − If queue is full, produce overflow error and exit.
● Step 3 − If queue is not full, increment rear pointer to point next empty space.
● Step 4 − Add data element to the queue location, where rear is pointing.
● Step 5 − return success.
{
if(isempty())
return 0;
int data = queue[front];
front = front + 1;
return data;
}
IMPLEMENTATION OF QUEUE
The implementation of queue is given below
JOB SCHEDULING
Queues are used for the creation of a job-queue by an operating-system (Figure 14). If operating-system
does not use, then the jobs are processed in the order they enter the system.
a. shifting the jobs as the job leaves the queue and recalculating front and rear values, but shifting is very
time consuming b. another solution to the above problem is using circular queue.
2.8 CIRCULAR QUEUE
Circular queue is a linear data structure. It follows FIFO principle. In circular queue the last node is
connected back to the first node to make a circle. Circular linked list follow the First In First Out
principle.(as shown in figure 15) Elements are added at the rear end and the elements are deleted at front
end of the queue. The queue is considered as a circular queue when the positions 0 and MAX-1 are
adjacent. Any position before front is also after rear.
A circular queue looks like
This shortcoming can be overcome by using a dynamically allocated array for the elements & then
increasing the size of the array as needed. In array-doubling, double array-capacity is used whenever it
becomes necessary to increase the capacity of an array (Figure 16). [ Program A and Program B contains
code for circular array implementation using dynamic array]
Program A: Addition operation on CQ
void addq(element item)
{
rear= (rear+1) % capacity;
if(front == rear)
queueFull();
queue[rear]=item;
}
2.10 DEQUEUES
The word dequeue is short form of double ended queue. In a dequeue, insertion as well as deletion can
be carried out either at the rear end or the front end.(as shown in figure 17)
Operations on a Dequeue
● initialize(): Make the queue empty
● empty(): Determine if queue is empty
● full(): Determine if queue is full
● enqueueF(): Insert an element at the front end of the queue
● enqueueR(): Insert an element at the rear end of the queue
● dequeueR(): Delete the rear element
● dequeueF(): Delete the front element
● print(): Print elements of the queue
● An output-restricted dequeue is one where insertion can be made at both ends, but deletion can
be made from one end only.
Both the basic and most common list types in computing, queues and stacks can be considered
specializations of dequeues, and can be implemented using deques.
2.11 PRIORITY QUEUE
Priority Queue is more specialized data structure than Queue. Like ordinary queue, priority queue has
same method but with a major difference. In Priority queue items are ordered by key value so that item
with the lowest value of key is at front and item with the highest value of key is at rear or vice versa. So
we're assigned priority to item based on its key value. Lower the value, higher the priority. Following are
the principal methods of a Priority Queue.(as shown in figure 18)
Basic Operations
● insert / enqueue − add an item to the rear of the queue.
● remove / dequeue − remove an item from the front of the queue.
Representations of priority queue
1. Priority Queue array Representation
2. If no such node is found , insert ITEM as the last element of the list.
● The rat starts at the top left and is to exit at the bottom right. With the maze represented as 2D array,
the location of the rat in the maze can at any time be described by the row and column position,
● If X is the spot of our current position, maze[row][col]. Following figure represents the possible
moves from this position. It points to specify the 8 directions of movements:- N, NE, E, SE, S, SW,
W, NW as shown.
● The border conditions of the maze can be represented by a border of ones. Thus, an mxp maze will
require an (m+2)x(p+2) array. The entrance is at position[1][1] and exit at [m][p].
typedef struct
{
short int vert;
short int horiz;
}offsets;
offsets moves[8];
● If the rat is at position maze[row][col] and the next move position can be obtained as follows:-
● The function uses a variable found that is initially set to 0 (FALSE). If we find a path through the
maze, then set this variable to TRUE, thereby allowing to exit both while loops.
found – TRUE;
elseif(!maze[nextRow][nextCol] && !mark[nextRow][nextCol])
{
mark[nextRow][nextCol] = 1;
position.row = row;
position.col = col;
position.dir = ++dir;
push(position);
row=nextRow;
col=nextCol;
dir=0;
}
else ++dir;
}
}
if(found)
{
printf(“the path is:\n”);
printf(“row col\n”);
for(i=0;i<=top;i++)
printf(“%2d %5d\n”, stack[i].row, stack[i].col);
printf(“%2d %5d\n”, row, col);
printf(“%2d %5d\n”, EXIT_ROW, EXIT_COL);
}
else
printf(“The maze doesnot have a path”);
}
top[j]=boundary[j]=(MEMORY_SIZE/n)*j;
boundary[n]=MEMORY_SIZE-1;
Figure 25: Configuration when stack I meets stack i+1,but the memory is not full
In push function, top[i]==boundary[i+1] condition 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).(as shown in figure 24).Therefore, an error recovery function is created,
stackFull, which determines if there is any free space in memory. If there is space available, it should
shift the stacks so that space is allocated to the full stack. We can guarantee that stackFull adds elements
as long as there is free space in array memory if we:
1) Determine the least j, i<j<n such that there is free space between stacks j and j+1 i.e.
top[j]<boundary[j+1]. If there is such a j, then move stacks i+1,i+2 . . . . . .j one position to the right.
This creates a space between stacks i and i+1.
2) If there is no j as in (i),then look to the left of stack i. Find the largest j such that 0<=j<I and there is
space between stacks j and j+1 i.e. top[j]<boundary[j+1]. If there is such a j, then move stacks j+1,j+2. .
. . . .i one space to the left. This also creates a space between stacks I and i+1.
3) If there is no j satisfying either (i) or (ii),then all MEMORY_SIZE spaces of memory utilized and
there is no free space. In this case, stackFull terminates with an error message.