FDS Unit 5 Notes
FDS Unit 5 Notes
Unit –V
Stack
1. Stack
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.
It behaves like a stack of plates, where the last plate added is the first one to be removed. Think
of it this way:
Pushing an element onto the stack is like adding a new plate on top.
In Stack ADT Implementation instead of data being stored in each node, the pointer to
data is stored.
The program allocates memory for the data and address is passed to the stack ADT.
The head node and the data nodes are encapsulated in the ADT. The calling function
can only see the pointer to the stack.
The stack head structure also contains a pointer to top and count of number of entries
currently in stack.
2|Page © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
pop() – Remove and return the element at the top of the stack, if it is not empty.
peek() – Return the element at the top of the stack without removing it, if the stack is
not empty.
Stack follows LIFO (Last In First Out) Principle so the element which is pushed last is popped
first.
Check if the stack is full (if implemented with a fixed size). If not, place the element at the top.
Check if the stack is empty. If not, remove and return the top element.
5. isFull(): Checks whether the stack has reached its maximum capacity (for fixed-size
stacks).
} else {
return stack[top];
}
}
// Example usage
void main() {
push(5); // Push 5 onto the stack
push(10); // Push 10 onto the stack
print(peek()); // Output: 10
print(pop()); // Output: 10
print(pop()); // Output: 5
print(isEmpty()); // Output: true
}
1.5 Stack Implementation Using an Array
To implement a stack using an array, initialize an array and treat its end as the stack’s top. The
basic operations for a stack include push, pop, and peek, with appropriate handling for empty
and full stack conditions.
Step-by-Step Approach:
1. Initialize an Array: An array is used to represent the stack. The end of the array represents
the top of the stack.
2. push Operation:
o Overflow Condition: Before adding an element, check if the stack is full (i.e., top ==
capacity - 1). If the stack is full, it leads to a stack overflow, and no further elements
can be added.
o If space is available, increment the top pointer and insert the element at the new top
position.
3. pop Operation:
o Underflow Condition: Before popping, check if the stack is empty (i.e., top == -1).
If the stack is empty, it leads to a stack underflow.
o If the stack is not empty, store the element at top, decrement the top pointer, and return
the popped value.
5. isEmpty Operation:
o If top == -1, the stack is empty, so return true; otherwise, return false.
6. isFull Operation:
o If top == capacity - 1, the stack is full, so return true; otherwise, return false.
Complexity Analysis:
Time Complexity:
o push: O(1)
o pop: O(1)
o peek: O(1)
8|Page © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
o is_empty: O(1)
o is_full: O(1)
The size of the stack is fixed at compile time, meaning it cannot grow or shrink
dynamically during runtime. However, dynamic arrays like vectors in C++ or
ArrayLists in Java can overcome this limitation, allowing the stack to grow or shrink
as needed.
Predefined stack size may lead to inefficient memory usage if the size is not fully
utilized.
Multiple Stacks:
Multiple stacks can be implemented using a single array. By dividing the array into sections,
each section can represent a separate stack. Each stack has its own top pointer to manage its
elements independently. This approach is useful when managing multiple stacks in programs
without allocating separate memory for each stack, making the memory usage more efficient.
Each stack operates independently within its designated section of the array.
Stack operations (push, pop, peek) are handled separately for each stack using its own
top pointer.
This method is memory efficient when multiple stacks are needed, as it minimizes
memory overhead by using a single array.
Example: C++ code to implement stack using array with overflow and underflow conditions.
Stacks are widely used in expression evaluation and conversion because they follow the
LIFO (Last In, First Out) principle, which matches the nested structure of expressions and
operations.
Expression Types:
Example: A + B * C
Example: A B C * +
Example: + A * B C
2. Expression Conversion:
Steps:
3. If an operator is encountered:
Pop operators from the stack (with higher or equal precedence) and
append them to the output.
6. After scanning, pop remaining operators and append them to the output.
Example:
10 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
o Input: A + B * C
o Output: A B C * +
Reverse the infix expression, convert it to postfix, and then reverse the result.
3. Expression Evaluation:
Stacks are used to evaluate postfix and prefix expressions because they eliminate the need for
parentheses.
a) Postfix Evaluation:
Steps:
2. If an operator is encountered:
Example:
o Input: 5 6 2 + *
o Process:
Push 5, 6, and 2.
b) Prefix Evaluation:
Applications:
1. Avoiding Parentheses:
o Infix [(A + B) * C]: Operators are between operands, requiring parentheses for
clarity and precedence.
Infix: (A + B) * C
Prefix: * + A B C
Postfix: A B + C *
2. Simplified Evaluation:
o Postfix/Prefix: Evaluated using stacks. In postfix, operands are pushed and operators
apply when encountered. In prefix, read from right to left.
3. No Operator Precedence:
5. Memory Efficiency:
o Stacks: Efficiently handle operands and operators sequentially, reducing memory usage
compared to infix evaluation.
Postfix notation, also known as Reverse Polish Notation (RPN), is a way of writing
arithmetic expressions without the need for parentheses. In a postfix expression, the operator
follows the operands. The key advantage of postfix expressions is that they can be evaluated
directly from left to right using a stack, without needing to worry about operator precedence
or parentheses.
o If the symbol is an operator, pop the required number of operands (usually two)
from the stack, perform the operation, and push the result back onto the stack.
4. At the end of the expression, the stack will contain only one element, which is the
result of the expression.
Step-by-Step Example:
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”.
13 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
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 = {+, *}.
14 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
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*+”.
15 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
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+”.
16 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
Time Complexity:
Time Complexity: O(n), where n is the number of elements in the postfix expression.
o Each operand is pushed once, and each operator pops operands and pushes the
result once, so the operations are linear in time.
Applications:
Expression Parsers: Postfix expressions are used in compilers and interpreters for
evaluating arithmetic expressions.
return postfix;
}
// Example usage
void main() {
string infix = "A*(B+C)-D/E";
print(infixToPostfix(infix)); // Output: ABC+*DE/-
}
Q. Convert expression (A* B – (C + D * E) ˆ (F * G / H)) stepwise using above
rules. Where ˆ is - exponential operator.
Operator Precedence:
Step-by-Step Conversion:
1. Original Expression: A * B – (C + D * E) ^ (F * G / H)
2. Handle Parentheses:
o Solve (C + D * E):
D * E → DE*
19 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
C + (DE*) → CDE*+
o Solve (F * G / H):
F * G → FG*
(FG*) / H → FG*H/
3. Handle Exponentiation:
o A * B → AB*
AB*CDE*+FG*H/^–
Key Points:
Operators are placed after their operands, respecting precedence and associativity rules.
To implement a stack using the singly linked list concept, all the singly linked list operations
should be performed based on Stack operations LIFO (Last In First Out) and with the help of
that knowledge, we are going to implement a stack using a singly linked list.
So, we need to follow a simple rule in the implementation of a stack which is last in first out
and all the operations can be performed with the help of a top variable.
20 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
In the stack Implementation, a stack contains a top pointer. which is the “head” of the stack
where pushing and popping items happens at the head of the list. The first node has a null in
the link field and second node-link has the first node address in the link field and so on and the
last node address is in the “top” pointer.
The main advantage of using a linked list over arrays is that it is possible to implement a stack
that can shrink or grow as much as needed. Using an array will put a restriction on the
maximum capacity of the array which can lead to stack overflow. Here each new node will be
dynamically allocated. so, overflow is not possible.
Stack Operations:
21 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
push(): Insert a new element into the stack i.e just insert a new element at the beginning of
the linked list.
pop(): Return the top element of the Stack i.e simply delete the first element from the linked
list.
Push Operation:
Initialize a node.
Pop Operation:
First check if there is any node present in the linked list or not, if not then return.
Otherwise make pointer say temp to the top node and move forward the top node by
one step.
Peek Operation:
Display Operation:
struct Node {
int data; // Data part of the node
Node* next; // Pointer to the next node
};
// Example usage
void main() {
push(10); // Push 10 onto the stack
push(20); // Push 20 onto the stack
print(peek()); // Output: 20
print(pop()); // Output: 20
print(pop()); // Output: 10
print(pop()); // Output: Stack Underflow!
}
2. Recursion
24 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
2.1 Concept
Recursion is a programming technique where a function calls itself to solve smaller instances
of a problem. It divides a complex problem into smaller subproblems until a base condition is
met, which stops further recursive calls.
Key Components:
1. Base Case: The condition where the recursion stops to prevent infinite calls.
2. Recursive Case: The part where the function calls itself with smaller inputs.
Advantages:
Simplifies code for problems like tree traversal, factorial, Fibonacci, etc.
Disadvantages:
Can lead to stack overflow if the base case isn't defined or too many recursive calls are
made.
Recursion are mainly of two types depending on whether a function calls itself from within
itself or more than one function call one another mutually. The first one is called direct
recursion and another one is called indirect recursion. Thus, the two types of recursion are:
Tail Recursion: If a recursive function calls itself and that recursive call is the last statement
in the function then it’s known as Tail Recursion. After that call the recursive function
performs nothing. The function has to process or perform any operation at the time of calling
and it does nothing at returning time.
Example:
// Recursion function
void fun(int n) {
if (n > 0) {
cout << n << " ";
25 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
// Driver Code
int main() {
int x = 3;
fun(x);
return 0;
}
Let’s understand the example by tracing tree of recursive function. That is how the calls are
made and how the outputs are produced.
Head Recursion: If a recursive function is calling itself and that recursive call is the first
statement in the function then it’s known as Head Recursion. There’s no statement, no
operation before the call. The function doesn’t have to process or perform any operation at the
time of calling and all operations are done at returning time.
Example:
// Recursive function
26 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
void fun(int n)
{
if (n > 0) {
// Driver code
int main()
{
int x = 3;
fun(x);
return 0;
}
Let’s understand the example by tracing tree of recursive function. That is how the calls are
made and how the outputs are produced.
27 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
Tree Recursion: To understand Tree Recursion let’s first understand Linear Recursion. If a
recursive function calls itself for one time, then it is known as Linear Recursion. Otherwise,
if a recursive function calls itself for more than one time then it’s known as Tree Recursion.
Example:
void fun(int n)
{
if (n > 0)
{
cout << " " << n;
// Calling once
fun(n - 1);
// Calling twice
fun(n - 1);
}
}
// Driver code
int main()
{
fun(3);
return 0;
}
Let’s understand the example by tracing tree of recursive function. That is how the calls are
made and how the outputs are produced.
28 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
2. Indirect Recursion: In this recursion, there may be more than one functions and they are
calling one another in a circular manner.
Example:
void funA(int n)
{
if (n > 0) {
cout <<" "<< n;
void funB(int n)
{
if (n > 1) {
cout <<" "<< n;
}
}
// Driver code
int main()
{
funA(20);
return 0;
}
Let’s understand the example by tracing tree of recursive function. That is how the calls are
made and how the outputs are produced.
Steps in Backtracking:
30 | P a g e © Haris Chaus | ALL RIGHTS ARE RESERVED as per copyright act.
4. Backtrack: Undo the last step (partial solution) and try another path.
Working of Backtracking:
As shown in the image, “IS” represents the Initial State where the recursion call starts to find
a valid solution. “C” represents different Checkpoints for recursive calls
“TN” represents the Terminal Nodes where no further recursive calls can be made, these nodes
act as base case of recursion and we determine whether the current solution is valid or not at
this state.
At each Checkpoint, our program makes some decisions and move to other checkpoints until
it reaches a terminal Node, after determining whether a solution is valid or not, the program
starts to revert back to the checkpoints and try to explore other paths.
For example, in the image TN1…TN5 are the terminal node where the solution is not
acceptable, while TN6 is the state where we found a valid solution.
The back arrows in the image show backtracking in actions, where we revert the changes made
by some checkpoint.
Backtracking inherently relies on a stack structure, either explicitly or through the function
call stack during recursion.
1. State Management: The stack keeps track of the current state of the problem at each
decision point (e.g., the current position, chosen options, etc.).
2. Undo Operations: When backtracking, the stack allows restoring the previous state by
popping elements, making it easy to explore alternative paths.
Using a Stack:
Each stack frame represents the current board state and decisions made (e.g., placing a
queen on a specific row).
Advantages of Backtracking:
Applications of Backtracking:
N-Queens Problem
Sudoku Solver