DSA Module 2
DSA Module 2
Module-2
Stacks: Definition, Stack Operations, Array Representation of Stacks, Stacks using Dynamic
Arrays, Stack Applications: Polish notation, Infix to postfix conversion, evaluation of postfix
expression.
Queues: Definition, Array Representation, Queue Operations, Circular Queues, Circular queues
using Dynamic arrays, Deque, Priority Queues and its problems
What is a Stack?
● A Stack is a linear data structure that follows the LIFO (Last-In-First-Out) principle.
● Stack has one end, whereas the Queue has two ends (front and rear).
● It contains only one pointer top pointer pointing to the topmost element of the stack.
● Whenever an element is added in the stack, it is added on the top of the stack, and the
element can be deleted only from the stack.
● In other words, a stack can be defined as a container in which insertion and deletion can
be done from the one end known as the top of the stack.
1
Some key points related to stack
○ It is called as stack because it behaves like a real-world stack, piles of books, etc.
○ A Stack is an abstract data type with a pre-defined capacity, which means that it can store
the elements of a limited size.
○ It is a data structure that follows some order to insert and delete the elements, and that
order can be LIFO.
2
Working of Stack
● Stack works on the LIFO pattern.
● As we can observe in the below figure there are five memory blocks in the stack; therefore,
the size of the stack is 5.
● Suppose we want to store the elements in a stack and let's assume that stack is empty.
3
● We have taken the stack of size 5 as shown below in which we are pushing the elements
one by one until the stack becomes full.
○ push(): When we insert an element in a stack then the operation is known as a push. If the
stack is full then the overflow condition occurs.
○ pop(): When we delete an element from the stack, the operation is known as a pop. If the
stack is empty means that no element exists in the stack, this state is known as an
underflow state.
4
○ isEmpty(): It determines whether the stack is empty or not.
AD
PUSH operation
The steps involved in the PUSH operation is given below:
○ If we try to insert the element in a stack, and the stack is full, then the overflow condition
occurs.
○ When we initialize a stack, we set the value of top as -1 to check that the stack is empty.
○ 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.
○ The elements will be inserted until we reach the max size of the stack.
5
POP operation
The steps involved in the POP operation is given below:
○ Before deleting the element from the stack, we check whether the stack is empty.
○ If we try to delete the element from the empty stack, then the underflow condition
occurs.
○ If the stack is not empty, we first access the element which is pointed by the top ○ Once the
6
Applications of Stack
The following are the applications of the stack:
1. Balancing of symbols: Stack is used for balancing a symbol. For example, we have the following
program:
int main()
cout<<"Hello"; cout<<"Parul";
7
○
○ As we know, each program has an opening and closing braces; when the opening
braces come, we push the braces in a stack, and when the closing braces appear,
we pop the opening braces from the stack. Therefore, the net value comes out to
be zero. If any symbol is left in the stack, it means that some syntax error occurs in
a program.
2. String reversal:
○ For example, we want to reverse a "Parul" string, so we can achieve this with the
help of a stack.
○ First, we push all the characters of the string in a stack until we reach the null
character.
○ After pushing all the characters, we start taking out the character one by one until
we reach the bottom of the stack.
8
3. UNDO/REDO:
9
○ It can also be used for performing UNDO/REDO operations. For example, we have
an editor in which we write 'a', then 'b', and then 'c'; therefore, the text written
in an editor is abc. So, there are three states, a, ab, and abc, which are stored in a
stack. There would be two stacks in which one stack shows UNDO state, and the
other shows REDO state.
If we want to perform UNDO operation, and want to achieve 'ab' state, then we
implement pop operation.
10
4. Recursion:
○ The recursion means that the function is calling itself again.
○ To maintain the previous states, the compiler creates a system stack in which all
the previous records of the function are maintained.
11
5. DFS(Depth First Search): This search is implemented on a Graph, and Graph uses the stack data
structure.
12
13
6. Backtracking: Suppose we have to create a path to solve a maze problem. If we are moving in a
particular path, and we realize that we come on the wrong way. In order to come at the beginning of
the path to create a new path, we have to use the stack data structure.
14
15
7. Expression conversion:
Stack can also be used for expression conversion. This is one of the most important
applications of stack. The list of the expression conversion is given below:
Infix to prefix
Infix to postfix
Prefix to infix
8. Memory management:
16
● The stack manages the memory.
● The memory is assigned in the contiguous memory blocks. The memory is known as
stack memory as all the variables are assigned in a function call stack memory.
● When the function is created, all its variables are assigned in the stack memory.
● When the function completed its execution, all the variables assigned in the stack are
released.
stack are performed using arrays. Lets see how each operation can be implemented on the stack
using array data structure.
1. Increment the variable Top so that it can now refere to the next memory location.
2. Add element at the position of incremented top. This is referred to as adding new element
at the top of the stack.
Stack is overflown when we try to insert an element into a completely filled stack therefore, our
main function must always avoid stack overflow condition.
Algorithm:
17
begin if top = n then stack
= item; end
if (top == n ) printf("\n
Overflow"); else
= val;
18
Deletion of an element from a stack (Pop operation)
Deletion of an element from the top of the stack is called pop operation. The top most element
of the stack is stored in an another variable and then the top is decremented by 1. the operation
returns the deleted value that was stored in another variable as the result.
The underflow condition occurs when we try to delete an element from an already empty stack.
Algorithm :
top - 1; end;
19
int pop ()
if(top == -1)
{ printf("Underflow"); return
0;
else
return stack[top - - ];
Algorithm :
PEEK (STACK, TOP)
20
Begin if top = -1 then stack
return item
End
Time complexity: o(1) -> For Peek & o(n) -> for visiting each element
21
Implementation of Peek algorithm in C language
int peek()
if (top == -1)
{ printf("Underflow"); return
0;
else
22
#include <stdio.h> int
stack[100],i,j,choice=0,n,top=-1; void
()
array*********");
printf("\n----------------------------------------------\n"); while(choice
!= 4)
printf("\n1.Push\n2.Pop\n3.Show\n4.Exit"); printf("\n
switch(choice)
23
case 1:
push(); break;
case 2:
pop(); break;
case 3:
show(); break;
case 4:
{ printf("Exiting....");
break;
default:
24
}
};
void push ()
= val;
void pop ()
25
else top = top
-1;
void show()
{ for (i=top;i>=0;i--)
{ printf("%d\n",stack[i]);
if(top == -1)
printf("Stack is empty");
In linked list implementation of stack, the nodes are maintained non-contiguously in the memory.
Each node contains a pointer to its immediate successor node in the stack. Stack is said to be
overflown if the space left in the memory heap is not enough to create a node.
26
The top most node in the stack always contains null in its address field. Lets discuss the way in
which, each operation is performed in linked list implementation of stack.
We’ll discuss the below points after completion of our module- 3 (LinkedList):
● Menu Driven program in C implementing all the stack operations using linked lis t
27
Examples of expressions are:
5+6A
-B
(P * 5)
All the above expressions have a common structure, i.e., we have an operator between the two
operands. An Operand is an object or a value on which the operation is to be performed. In the
above expressions, 5, 6 are the operands while '+', '-', and '*' are the operators.
For example,
(p + q) * (r + s)
In the above expression, both the expressions of the multiplication operator are the operands,
i.e., (p + q), and (r + s) are the operands.
In the above expression, there are three operators. The operands for the first plus operator are p
and q, the operands for the second plus operator are r and s. While performing the operations
on the expression, we need to follow some set of rules to evaluate the result. In the above
expression, addition operation would be performed on the two expressions, i.e., p+q and r+s, and
then the multiplication operation would be performed.
AD
28
Following the operator precedence rules. In the algebraic expression, the order of the operator
precedence is given in the below table:
Operators Symbols
Parenthesis ( ), {}, [ ]
Exponents ^
The first preference is given to the parenthesis; then next preference is given to the exponents.
In the case of multiple exponent operators, then the operation will be applied from right to left.
For example:
2^2^3 = 2 ^ 8
= 256
29
Problem with infix notation
● To evaluate the infix expression, we should know about the operator precedence rules,
and if the operators have the same precedence, then we should follow the associativity
rules.
● The use of parenthesis is very important in infix notation to control the order in which the
operation to be performed.
● Parenthesis improves the readability of the expression.
● An infix expression is the most common way of writing expression, but it is not easy to
parse and evaluate the infix expression without ambiguity.
● So, mathematicians and logicians studied this problem and discovered two other ways of
writing expressions which are prefix and postfix.
● Both expressions do not require any parenthesis and can be parsed without ambiguity.
● It does not require operator precedence and associativity rules.
Postfix notation:
30
31
32
20 practice questions on the conversion of “Infix to Postfix”:
1. A + B * C - D
2. (A + B) * (C - D) / E
3. A * (B + C) - D / (E + F)
4. (A + B * C) - D / E
5. A * B + C / (D - E) * F
6. A + (B - C) * D + E
7. (A + B) * C + D - E / F
8. A * (B - C) + D / (E * F)
9. A + B * C - D / (E + F) * G
10. (A + B * C) - D / (E + F) * G 11. A * (B + C - D) - E / F + G
12. (A + B) * (C - D) + E / F - G
13. A + (B - C) * (D + E) - F / G
14. (A * B + C) - D / (E - F) * (G + H)
15. A * (B + C) - (D / E) + F * G - H
16. (A + B * C - D) / (E - F * G) + H
17. A * B + (C - D) / (E + F) - G * H + I
18. (A + B) * (C - D * E + F) - G + H / I
19. A + B * C - (D / E + F) * G - H + I * J
20. (A + B * C - D) / (E - F * G + H) * I - J
Recursion in C
● Recursion is the process which comes into existence when a function calls a copy of itself
to work on a smaller problem.
● Any function which calls itself is called recursive function, and such function calls are called
recursive calls.
● Recursion involves several numbers of recursive calls. However, it is important to impose
a termination condition of recursion.
33
● Recursion cannot be applied to all the problem, but it is more useful for the tasks that can
be defined in terms of similar subtasks.
● For Example, recursion may be applied to sorting, searching, and traversal problems.
#include <stdio.h>
34
printf("factorial = %d",f);
int fact(int n)
if (n==0)
return 0;
} else if ( n ==
1)
return 1;
else
return n*fact(n-1);
Output:
Recursive Function
● A recursive function performs the tasks by dividing it into the subtasks.
● There is a termination condition defined in the function which is satisfied by some specific
subtask.
● After this, the recursion stops and the final result is returned from the function.
● The case at which the function doesn't recur is called the base case whereas the instances
where the function keeps calling itself to perform a subtask, is called the recursive case.
if (test_for_base)
return some_value;
36
}
else if (test_for_another_base)
return some_another_value;
else
// Statements; recursive
call;
Example of recursion in C
Let's see an example to find the nth term of the Fibonacci series.
37
#include<stdio.h> int
()
n?"); scanf("%d",&n); f =
fibonacci(n); printf("%d",f);
if (n==0)
return 0;
38
}
else if (n == 1)
return 1;
else
return fibonacci(n-1)+fibonacci(n-2);
Output
Enter the value of n?12
144
Let's see an example to find the nth term of the GCD (Greatest Common Divisor).
● GCD (Greatest Common Divisor) is the largest positive integer that divides two or
more integers without leaving a remainder.
● We can compute the GCD of two numbers using recursion and the Euclidean
algorithm.
● The Euclidean algorithm states that the GCD of two numbers a and b is equal to
the GCD of b and the remainder of dividing a by b.
● This process is repeated until the remainder becomes 0, at which point the GCD is
found.
39
#include <stdio.h>
%d\n", result);
return 0;
}
Tower of Hanoi
1. It is a classic problem where you try to move all the disks from one peg to another peg
using only three pegs.
2. Initially, all of the disks are stacked on top of each other with larger disks under the smaller
disks.
3. You may move the disks to any of three pegs as you attempt to relocate all of the disks,
but you cannot place the larger disks over smaller disks and only one disk can be transferred at
a time.
40
In the above 7 step all the disks from peg A will be transferred to C given Condition:
41
#include <stdio.h>
// Move numDisks-1 disks from source to auxiliary using destination as the auxiliary peg
towerOfHanoi(numDisks - 1, source, destination, auxiliary);
// Move the numDisks-1 disks from auxiliary to destination using source as the
auxiliary peg towerOfHanoi(numDisks - 1, auxiliary, source, destination);
}
return 0;
}
42
Queues
1. A queue can be defined as an ordered list which enables insert operations to be performed at
one end called REAR and delete operations to be performed at another end called FRONT.
3. For example, people waiting in line for a rail ticket form a queue.
43
Applications of Queue
Due to the fact that queue performs actions on first in first out basis which is quite fair for the
ordering of actions. There are various applications of queues discussed as below.
1. Queues are widely used as waiting lists for a single shared resource like printer, disk, or
CPU.
2. Queues are used in asynchronous transfer of data (where data is not being transferred at
the same rate between two processes) for eg. pipes, file IO, sockets.
3. Queues are used as buffers in most of the applications like MP3 media player, CD player,
etc.
4. Queue are used to maintain the play list in media players in order to add and remove the
songs from the play-list.
what is a Queue?
● 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.
44
●
○ Dequeue: It performs the deletion from the front-end of the queue. It also returns the
element which has been removed from the front-end. It returns an integer value.
○ Peek: This is the third operation that returns the element, which is pointed by the front
pointer in the queue but does not delete it.
○ Queue overflow (isfull): It shows the overflow condition when the queue is completely
full.
45
○ Queue underflow (isempty): It shows the underflow condition when the Queue is empty,
i.e., no elements are in the Queue.
Types of Queue
There are four different types of queue that are listed as follows -
○ Circular Queue
○ Priority Queue
● 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.
46
● 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.
Circular Queue
● In Circular Queue, all the nodes are represented as circular. It is similar to the linear Queue
except that the last element of the queue is connected to the first element.
● It is also known as Ring Buffer, as all the ends are connected to another end.
So, to overcome such limitations, the concept of the circular queue was introduced.
47
● The drawback that occurs in a linear queue is overcome by using the circular queue.
● If the empty space is available in a circular queue, the new element can be added in an
empty space by simply incrementing the value of rear.
● The main advantage of using the circular queue is better memory utilization.
○ enQueue(value): This function is used to insert the new value in the Queue. The new
element is always inserted from the rear end.
48
○ deQueue(): This function deletes an element from the Queue. The deletion in a Queue
always takes place from the front end.
Enqueue operation
The steps of enqueue operation are given below:
○ Initially the front and rear are set to -1. When we insert the first element in a Queue, front
and rear both are set to 0.
○ When we insert a new element, the rear gets incremented, i.e., rear=rear+1.
○ If rear != max - 1, then rear will be incremented to mod(maxsize) and the new value will
be inserted at the rear end of the queue.
○ If front != 0 and rear = max - 1, it means that queue is not full, then set the value of rear
to 0 and insert the new element there.
○ When front ==0 && rear = max-1, which means that front is at the first position of the
Queue and rear is at the last position of the Queue.
○ front== rear + 1;
49
50
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 -
51
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.
○ Every element in a priority queue has some priority associated with it.
○ An element with the higher priority will be deleted before the deletion of the lesser
priority.
○ If two elements in a priority queue have the same priority, they will be arranged using the
FIFO principle.
1, 3, 4, 8, 14, 22
All the values are arranged in ascending order. Now, we will observe how the priority queue will
look after performing the following operations:
52
○ poll(): This function will remove the highest priority element from the priority queue. In
the above priority queue, the '1' element has the highest priority, so it will be removed
from the priority queue.
○ add(2): This function will insert '2' element in a priority queue. As 2 is the smallest
element among all the numbers so it will obtain the highest priority.
○ poll(): It will remove '2' element from the priority queue as it has the highest priority
queue.
○ add(5): It will insert 5 element after 4 as 5 is larger than 4 and lesser than 8, so it will
obtain the third highest priority in a priority queue.
● It means that we can insert and delete elements from both front and rear ends of the
queue.
● Deque can be used as a palindrome checker means that if we read the string from both
ends, then the string would be the same.
● 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.
53
There are two types of deque that are discussed as follows -
AD
○ 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.
○ 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.
54
Operations performed on deque
There are the following operations that can be applied on a deque -
○ Insertion at front
○ Insertion at rear
○ Deletion at front
○ Deletion at rear
Addition to the above operations, the following operations are also supported in deque -
● There are two variables i.e. front and rear, that are implemented in the case of every
queue.
● Initially, the value of front and queue is -1 which represents an empty queue.
55
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".
After deleting an element, the value of front will increase from 0 to 1. however, the queue will
look something like following.
56
Algorithm to insert any element in a queue
Check if the queue is already full by comparing rear to max - 1. if so, then return an overflow error.
If the item is to be inserted as the first element in the list, in that case set the value of front and
rear to 0 and insert the element at the rear end.
Otherwise keep increasing the value of rear and insert each element one by one having rear as
the index.
Algorithm
57
[END OF IF]
○ Step 4: EXIT
Otherwise, keep increasing the value of front and return the item stored at the front end of the
queue at each time.
Algorithm
○ Step 2: EXIT
58
#include<stdlib.h> #define
()
59
{
!= 4)
printf("\n*************************Main Menu*****************************\n");
switch(choice)
case 1:
insert();
break; case
2:
delete(); break;
60
case 3:
display();
break; case
4:
exit(0); break;
default:
void insert()
element\n");
maxsize-1)
printf("\nOVERFLOW\n"); return;
front = 0; rear
= 0;
else
rear = rear+1;
inserted ");
void delete()
printf("\nUNDERFLOW\n"); return;
else
== rear)
else
front = front + 1;
void display()
{ int i; if(rear
== -1)
printf("\nEmpty queue\n");
for(i=front;i<=rear;i++)
○ Memory wastage: The space of the array, which is used to store queue elements, can
never be reused to store the elements of that queue because the elements can only be
○ One of the most common problems with array implementation is the array size,
which must be declared in advance.
○ Due to this reason, we can declare the array large enough so that we can store queue
elements as enough as possible but the main problem with this declaration is that,
most of the array slots (nearly half) can never be reused.