Unit 2
Unit 2
Unit 2
Er Asim Ahmad
Assistant Professor, CSE
Shri Ramswaroop Memorial College of Engineering &
Management
STACK DATA STRUCTURE
A stack is a linear data structure that follows the principle of Last In First Out (LIFO). This
means the last element inserted inside the stack is removed first. This strategy states that the
element that is inserted last will come out first. You can take a pile of plates kept on top of each
other as a real-life example. The plate we put last is on the top, and since we remove the plate at
the top, we can say that the plate that was put last comes out first.
In programming terms, putting an item on top of the stack is called pushing the element and removing
an item is called popping the element. So, in Stack, these two operations are very important.
In order to make manipulations in a stack, there are some basic operations that allow us to perform
different actions on a stack.
Pop
Removes an item from the stack. The items are popped in the reversed order in which they are pushed. If
the stack is empty, then it is said to be an Underflow condition.
IsEmpty
Returns true if the stack is empty, else false.
IsFull
Removes an item from the stack. The items are popped in the reversed order in which they are pushed. If
the stack is empty, then it is said to be an Underflow condition.
Peek
Returns the top or peek element of the stack.
For a better understanding of Stack, here is a sample program for the push() and pop() functions in Stack as
follows:
C PROGRAM FOR IMPLEMENTATION OF STACK
return popped;
}
int peek(struct StackNode* root)
{
if (isEmpty(root))
return
INT_MIN;
return root->data;
}
int main()
{
struct StackNode* root = NULL;
push(&root, 10);
push(&root, 20);
push(&root, 30);
return 0;
}
Types of Stacks:
Fixed Size Stack: As the name suggests, a fixed size stack has a fixed size and cannot grow or shrink
dynamically. If the stack is full and an attempt is made to add an element to it, an overflow error
occurs. If the stack is empty and an attempt is made to remove an element from it, an underflow error
occurs.
Dynamic Size Stack: A dynamic size stack can grow or shrink dynamically. When the stack is full, it
automatically increases its size to accommodate the new element, and when the stack is empty, it
decreases its size. This type of stack is implemented using a linked list, as it allows for easy resizing
of the stack.
In addition to these two main types, there are several other variations of Stacks, including:
1. Infix to Postfix Stack: This type of stack is used to convert infix expressions to postfix expressions.
2. Expression Evaluation Stack: This type of stack is used to evaluate postfix expressions.
3. Recursion Stack: This type of stack is used to keep track of function calls in a computer program and
to return control to the correct function when a function returns.
4. Memory Management Stack: This type of stack is used to store the values of the program counter
and the values of the registers in a computer program, allowing the program to return to the previous
state when a function returns.
5. Balanced Parenthesis Stack: This type of stack is used to check the balance of parentheses in an
expression.
6. Undo-Redo Stack: This type of stack is used in computer programs to allow users to undo and redo
actions.
Applications of the stack:
Infix to Postfix /Prefix conversion
Redo-undo features at many places like editors, photoshop.
Forward and backward features in web browsers
Used in many algorithms like Tower of Hanoi, tree traversals, stock span problems, and histogram
problems.
Backtracking is one of the algorithm designing techniques. Some examples of backtracking are the
Knight-Tour problem, N-Queen problem, find your way through a maze, and game-like chess or
checkers in all these problems we dive into someway if that way is not efficient we come back to the
previous state and go into some another path. To get back from a current state we need to store the
previous state for that purpose we need a stack.
In Graph Algorithms like Topological Sorting and Strongly Connected Components
In Memory management, any modern computer uses a stack as the primary management for a running
purpose. Each program that is running in a computer system has its own memory allocations
String reversal is also another application of stack. Here one by one each character gets inserted into
the stack. So the first character of the string is on the bottom of the stack and the last element of a
string is on the top of the stack. After Performing the pop operations on the stack we get a string in
reverse order.
Stack also helps in implementing function call in computers. The last called function is always
completed first.
Stacks are also used to implement the undo/redo operation in text editor.
Implementation of Stack:
A stack can be implemented using an array or a linked list. In an array-based implementation, the push
operation is implemented by incrementing the index of the top element and storing the new element at
that index. The pop operation is implemented by decrementing the index of the top element and
returning the value stored at that index. In a linked list-based implementation, the push operation is
implemented by creating a new node with the new element and setting the next pointer of the current top
node to the new node. The pop operation is implemented by setting the next pointer of the current top
node to the next node and returning the value of the current top node.
Stacks are commonly used in computer science for a variety of applications, including the evaluation of
expressions, function calls, and memory management. In the evaluation of expressions, a stack can be
used to store operands and operators as they are processed. In function calls, a stack can be used to keep
track of the order in which functions are called and to return control to the correct function when a
function returns. In memory management, a stack can be used to store the values of the program counter
and the values of the registers in a computer program, allowing the program to return to the previous
state when a function returns.
In conclusion, a Stack is a linear data structure that operates on the LIFO principle and can be
implemented using an array or a linked list. The basic operations that can be performed on a stack
include push, pop, and peek, and stacks are commonly used in computer science for a variety of
applications, including the evaluation of expressions, function calls, and memory management.There are
two ways to implement a stack –
Using array
Using linked list
2 (a + b) * c *+abc ab+c*
3 a * (b + c) *a+bc abc+*
5 (a + b) * (c + *+ab+cd ab+cd+*
d)
Parsing Expression
As we have discussed, it is not a very efficient way to design an algorithm or program to parse infix
notations. Instead, these infix notations are first converted into either postfix or prefix notations and then
computed. To parse any arithmetic expression, we need to take care of operator precedence and
associativity also.
As we know that the multiplication operator * has a higher precedence than the addition operator. First,
multiplication operator will move before operand B shown as below:
A+*BC
Once the multiplication operator is moved before 'B' operand, addition operator will move before the
operand 'A' shown as below:
+A*BC
Step 2: If the symbol pointed by 'S' is an operand then push it into the stack.
Step 3: If the symbol pointed by 'S' is an operator then pop two operands from the stack. Perform the
operation on these two operands and stores the result into the stack.
Step 4: Decrement the pointer 'S' by 1 and move to step 2 as long as the symbols left in the expression.
Step 5: The final result is stored at the top of the stack and return it.
Step 6: End
Expression: +, -, *, 2, 2, /, 16, 8, 5
Expression: 5, 8, 16, /, 2, 2, *, -, +
We will use the stack data structure to evaluate the prefix expression.
5 5
8 5, 8
16 5, 8, 16
/ 5, 2
2 5, 2, 2
2 5, 2, 2, 2
* 5, 2, 4
- 5, 2
+ 7
Postfix Notation
If we move the operators after the operands then it is known as a postfix expression. In other words,
postfix expression can be defined as an expression in which all the operators are present after the
operands. This notation style is known as Reversed Polish Notation. In this notation style, the operator
is postfixed to the operands i.e., the operator is written after the operands. For example, ab+. This is
equivalent to its infix notation a + b.
For example:
If the infix expression is A + B * C
As we know that the multiplication operator has a higher precedence than the addition operator, so
multiplication operator will move after the operands B and C shown as below:
A+BC*
Once the multiplication operator is moved after the operand C, then the addition operator will come after
the multiplication operator shown as below:
ABC*+
Evaluation of Postfix expression using Stack
Algorithm for the evaluation of postfix expression using stack:
Step 1: Create an empty stack used for storing the operands.
Step 2: Scan each element of an expression one be one and do the following:
o If the element is an operand then push it into the stack.
o If the element is an operator then pop two operands from the stack. Perform operation on these
operands. Push the final result into the stack.
Step 3: When the expression is scanned completely, the value available in the stack would be the final
output of the given expression.
Let's understand the evaluation of postfix expression using stack through an example.
If the expression is: 5, 6, 2, +, *, 12, 4, /, -
Symbol Scanned Stack
5 5
6 5, 6
2 5, 6, 2
+ 5, 8
* 40
12 40, 12
4 40, 12, 4
/ 40, 3
- 37
The result of the above expression is 37.
In order to parse any expression, we need to take care of two things, i.e., Operator
precedence and Associativity. Operator precedence means the precedence of any operator over another
operator. For example:
A + B * C → A + (B * C)
As the multiplication operator has a higher precedence over the addition operator so B * C expression will
be evaluated first. The result of the multiplication of B * C is added to the A.
Precedence order
Operators Symbols
Parenthesis { }, ( ), [ ]
Exponential notation ^
Multiplication and Division *, /
Addition and Subtraction +, -
Associativity means when the operators with the same precedence exist in the expression. For example, in
the expression, i.e., A + B - C, '+' and '-' operators are having the same precedence, so they are evaluated
with the help of associativity. Since both '+' and '-' are left-associative, they would be evaluated as (A + B)
- C.
Associativity order
Operators Associativity
^ Right to Left
*, / Left to Right
+, - Left to Right
1 + 2*3 + 30/5
Since in the above expression, * and / have the same precedence, so we will apply the associativity rule. As
we can observe in the above table that * and / operators have the left to right associativity, so we will scan
from the leftmost operator. The operator that comes first will be evaluated first. The operator * appears
before the / operator, and multiplication would be done first.
1+ (2*3) + (30/5)
1+6+6 = 13
If we are converting the expression from infix to prefix, we need first to reverse the expression.
To obtain the prefix expression, we have created a table that consists of three columns, i.e., input
expression, stack, and prefix expression. When we encounter any symbol, we simply add it into the prefix
expression. If we encounter the operator, we will push it into the stack.
Q Q
+ + Q
T + QT
* +* QT
V +* QTV
/ +*/ QTV
U +*/ QTVU
/ +*// QTVU
W +*// QTVUW
* +*//* QTVUW
) +*//*) QTVUW
P +*//*) QTVUWP
^ +*//*)^ QTVUWP
O +*//*)^ QTVUWPO
( +*//* QTVUWPO^
+ ++ QTVUWPO^*//*
N ++ QTVUWPO^*//*N
* ++* QTVUWPO^*//*N
M ++* QTVUWPO^*//*NM
- ++- QTVUWPO^*//*NM*
L ++- QTVUWPO^*//*NM*L
+ ++-+ QTVUWPO^*//*NM*L
K ++-+ QTVUWPO^*//*NM*LK
QTVUWPO^*//*NM*LK+-++
The above expression, i.e., QTVUWPO^*//*NM*LK+-++, is not a final expression. We need to reverse
this expression to obtain the prefix expression.
K K
+ +
L + KL
- - K L+
M - K L+ M
* -* K L+ M
N -* KL+MN
+ + K L + M N* -
( +( K L + M N *-
O +( KL+MN*-O
^ +(^ K L + M N* - O
P +(^ K L + M N* - O P
) + K L + M N* - O P ^
* +* K L + M N* - O P ^
W +* K L + M N* - O P ^ W
/ +/ K L + M N* - O P ^ W *
U +/ K L + M N* - O P ^W*U
/ +/ K L + M N* - O P ^W*U/
V +/ KL + MN*-OP^W*U/V
* +* KL+MN*-OP^W*U/V/
T +* KL+MN*-OP^W*U/V/T
+ + KL+MN*-OP^W*U/V/T*
KL+MN*-OP^W*U/V/T*+
Q + KL+MN*-OP^W*U/V/T*Q
KL+MN*-OP^W*U/V/T*+Q+
The final postfix expression of infix expression(K + L - M*N + (O^P) * W/U/V * T + Q) is KL+MN*-
OP^W*U/V/T*+Q+.
+ – ! ~ ++ —
2 Unary Right to Left
(type)* & sizeof
= += -+ *= /=
14 Assignment %= >>= <<= Right to Left
&= ^= |=
Here, we will see the conversion of prefix to postfix expression using a stack data structure.
Let's understand the conversion of Prefix to Postfix expression using Stack through an example.
The following are the steps required to convert postfix into prefix expression:
ab-c+
First, we scan the expression from left to right. We will move '-' operator before the operand ab.
-abc+
The next operator '+' is moved before the operand -abc is shown as below:
+-abc
The following are the steps used to convert postfix to prefix expression using stack:
Create an expression by concatenating two operands and adding operator before the operands.
o Repeat the above steps until we reach the end of the postfix expression.
AB + CD - *
Symbol
Action Stack Description
Scanned
A Push A into the stack A
B Push B into the stack AB
Pop B from the stack +AB Pop two operands from the stack, i.e., A and B.
+ Pop A from the stack Add '+' operator before the operands AB, i.e.,
Push +AB into the stack. +AB.
C Push C into the stack +ABC
D Push D into the stack +ABCD
Pop D from the stack. +AB -CD Pop two operands from the stack, i.e., D and C.
- Pop C from the stack. Add '-' operator before the operands CD, i.e., -CD.
Push -CD into the stack
Pop -CD from the stack. *+AB - Pop two operands from the stack, i.e., -CD and
Pop +AB from the stack. CD +AB. Add '*' operator before +AB then the
* expression would become *+AB-CD.
Push *+AB -CD into the
stack.
RECURSION
The process in which a function calls itself directly or indirectly is called recursion and the corresponding
function is called a recursive function. Using a recursive algorithm, certain problems can be solved quite
easily. Examples of such problems are Towers of Hanoi (TOH), Inorder/Preorder/Postorder Tree
Traversals, DFS of Graph, etc. A recursive function solves a particular problem by calling a copy of
itself and solving smaller sub problems of the original problems. Many more recursive calls can be
generated as and when required. We must know that we should provide a certain case to terminate this
recursion process. So, we can say that the function calls itself a simpler version of the original problem
every time. Recursion is an amazing technique with the help of which we can reduce the length of our
code and make it easier to read and write. It has certain advantages over the iteration technique which
will be discussed later. Recursion is one of the best solutions for a task that can be defined with its
similar subtask. For example; The Factorial of a number. A simple example of recursion would be:
int fib(int n)
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n;
printf("Which number do you want? ");
scanf("%d", &n);
printf("%d", fib(n));
return 0;
}
SIMULATION OF RECURSION
Simulation of a system is the operation of a model in terms of time or space, which helps analyse the
performance of an existing or proposed system. In other words, simulation is the process of using a
model to study the performance of a system. In our unit, recursion is the most important topic which we
have to observe through simulation.
A recursive function (or algorithm or method or procedure or routine) is a function (or algorithm or
method or procedure or routine) that calls/uses itself to get something done. Problems that can be broken
down into smaller problems of the same type can often be easily solved using recursion. Consider, for
example, a tree:
Tail Recursion
Tail recursion is defined as a recursive function in which the recursive call is the last statement that is
executed by the function. So basically nothing is left to execute after the recursion call.
Recursion Iteration
1. #include<stdio.h>
2. void printFibonacci(int n){
3. static int n1=0,n2=1,n3;
4. if(n>0){
5. n3 = n1 + n2;
6. n1 = n2;
7. n2 = n3;
8. printf("%d ",n3);
9. printFibonacci(n-1);
10. }
11. }
12. int main(){
13. int n;
14. printf("Enter the number of elements: ");
15. scanf("%d",&n);
16. printf("Fibonacci Series: ");
17. printf("%d %d ",0,1);
18. printFibonacci(n-2);//n-2 because 2 numbers are already printed
19. return 0;
20. }
TOWER OF HANOI PROBLEM
Tower of Hanoi, is a mathematical puzzle which consists of three towers (pegs) and more than one rings is
as depicted:
These rings are of different sizes and stacked upon in an ascending order, i.e. the smaller one sits over the
larger one. There are other variations of the puzzle where the number of disks increase, but the tower count
remains the same.
Rules
The mission is to move all the disks to some another tower without violating the sequence of arrangement.
A few rules to be followed for Tower of Hanoi are −
Only one disk can be moved among the towers at any given time.
Only the "top" disk can be removed.
No large disk can sit over a small disk.
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 the 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 (top) disk to aux peg.
Then, we move the larger (bottom) disk to destination peg.
And finally, we move the smaller disk from aux to destination peg.
So now, we are in a position to design an algorithm for Tower of Hanoi with more than two disks. We
divide the stack of disks in two parts. The largest disk (nth disk) is in one part and all other (n-1) disks
are in the second part. Our ultimate aim is to move disk n from source to destination and then put all
other (n1) disks onto it. We can imagine to apply the same in a recursive way for all given set of disks.
Similar to Stack, Queue is a linear data structure that follows a particular order in which the operations are
performed for storing data. The order is First In First Out (FIFO). One can imagine a queue as a line of
people waiting to receive something in sequential order which starts from the beginning of the line. It is an
ordered list in which insertions are done at one end which is known as the rear and deletions are done from
the other end known as the front. A good example of a queue is any queue of consumers for a resource
where the consumer that came first is served first. The difference between stacks and queues is in
removing. In a stack we remove the item the most recently added; in a queue, we remove the item the least
recently added.
Basic Operations on Queue:
enqueue(): Inserts an element at the end of the queue i.e. at the rear end.
dequeue(): This operation removes and returns an element that is at the front end of the queue.
front(): This operation returns the element at the front end without removing it.
rear(): This operation returns the element at the rear end without removing it.
isEmpty(): This operation indicates whether the queue is empty or not.
isFull(): This operation indicates whether the queue is full or not.
size(): This operation returns the size of the queue i.e. the total number of elements it contains.
Types of Queues:
Simple Queue: Simple queue also known as a linear queue is the most basic version of a queue. Here,
insertion of an element i.e. the Enqueue operation takes place at the rear end and removal of an element
i.e. the Dequeue operation takes place at the front end. Here problem is that if we pop some item from
front and then rear reach to the capacity of the queue and although there are empty spaces before front
means the queue is not full but as per condition in isFull() function, it will show that the queue is full
then. To solve this problem we use cirrrcular queue.
Circular Queue: In a circular queue, the element of the queue act as a circular ring. The working of a
circular queue is similar to the linear queue except for the fact that the last element is connected to the
first element. Its advantage is that the memory is utilized in a better way. This is because if there is an
empty space i.e. if no element is present at a certain position in the queue, then an element can be easily
added at that position using modulo capacity(%n).
Priority Queue: This queue is a special type of queue. Its specialty is that it arranges the elements in a
queue based on some priority. The priority can be something where the element with the highest value
has the priority so it creates a queue with decreasing order of values. The priority can also be such that
the element with the lowest value gets the highest priority so in turn it creates a queue with increasing
order of values. In pre-define priority queue, C++ gives priority to highest value whereas Java gives
priority to lowest value.
Dequeue: Dequeue is also known as Double Ended Queue. As the name suggests double ended, it
means that an element can be inserted or removed from both ends of the queue, unlike the other queues
in which it can be done only from one end. Because of this property, it may not obey the First In First
Out property.
Applications of Queue:
Queue is used when things don’t have to be processed immediately, but have to be processed
in First In First Out order like Breadth First Search. This property of Queue makes it also useful in
following kind of scenarios.
When a resource is shared among multiple consumers. Examples include CPU scheduling, Disk
Scheduling.
When data is transferred asynchronously (data not necessarily received at same rate as sent) between
two processes. Examples include IO Buffers, pipes, file IO, etc.
Queue can be used as an essential component in various other data structures.
Dequeue:
Accessing data from the queue is a process of two tasks: accessing the data where the front is pointing
and removing the data after access. The following steps are taken to perform the dequeue operation:
Algorithm for Dequeue operation
begin procedure dequeue
if queue is empty
return underflow
end if
data = queue[front]
front ← front + 1
return true
end procedure
IsEmpty:
While accessing the elements from a queue, we must check whether the queue is empty. If the value ofthe
front is less than MIN or 0, it tells that the queue is not yet initialized, hence empty:
Algorithm for IsEmpty operation
IsFull:
As we are using a single dimension array to implement a queue, we just check for the rear pointer to
reach MAXSIZE to determine that the queue is full. In case we maintain the queue in a circular linked list,
the algorithm will differ. Algorithm of isfull() function:
Algorithm for IsFull operation
Peek:
This function helps to see the data at the front of the queue. The algorithm is as follows:
// Structure to create a node with data and the next pointer struct
node {
int data;
struct node * next;
};
int main() {
int choice, value;
printf("\nImplementation of Queue using Linked List\n");
while (choice != 4) {
printf("1.Enqueue\n2.Dequeue\n3.Display\n4.Exit\n"); printf("\nEnter
your choice : ");
scanf("%d", &
choice); switch
(choice) {
case 1:
printf("\nEnter the value to insert: ");
scanf("%d", & value);
enqueue(value);
break;
case 2:
printf("Popped element is :%d\n", dequeue());
break;
case 3:
display()
; break;
case 4:
exit(0)
;
break;
default:
printf("\nWrong Choice\n");
}
}
return 0;
}
DEQUEUE (OR) DEQUE (DOUBLE-ENDED QUEUE)
Deque or Double Ended Queue is a type of queue in which insertion and removal of elements can either
be performed from the front or the rear. Thus, it does not follow the FIFO rule (First In First Out). So
Deque is a data structure that inherits the properties of both queues and stacks.
REPRESENTATION OF DEQUE
PRIORITY QUEUE
A priority queue is a special type of queue in which each element is associated with a priority value. And,
elements are served on the basis of their priority. That is, higher-priority elements are served first.
However, if elements with the same priority occur, they are served according to their order in the queue. It
is having a list of items in which each item has associated priority. It works on a principle that adds an
element to the queue with an associated priority and removes the element from the queue that has the
highest priority. In general, different items may have different priorities. In this queue highest or the
lowest priority item are inserted in random order. It is possible to delete an element from a priority
queue in order of their priorities starting with the highest priority.
Generally, the value of the element itself is considered for assigning priority. For example, the element
with the highest value is considered the highest-priority element. However, in other cases, we can
assume the element with the lowest value is the highest priority element.
🙤*🙤