Dsa (Week 5)
Dsa (Week 5)
Dsa (Week 5)
You can think of the stack data structure as the pile of plates on top of another.
And, if you want the plate at the bottom, you must first remove all the plates on top. This is
exactly how the stack data structure works.
LIFO Principle of Stack
In programming terms, putting an item on top of the stack is called push and removing an item is
called pop.
In the above image, although item 3 was kept last, it was removed first. This is exactly how
the LIFO (Last In First Out) Principle works.
Basic Operations of Stack
There are some basic operations that allow us to perform different actions on a stack.
1. A pointer called TOP is used to keep track of the top element in the stack.
2. When initializing the stack, we set its value to -1 so that we can check if the stack is
empty by comparing TOP == -1.
3. On pushing an element, we increase the value of TOP and place the new element in
the position pointed to by TOP.
4. On popping an element, we return the element pointed to by TOP and reduce its value.
To reverse a word - Put all the letters in a stack and pop them out. Because of the
LIFO order of stack, you will get the letters in reverse order.
In compilers - Compilers use the stack to calculate the value of expressions like 2
+ 4 / 5 * (7 - 9) by converting the expression to prefix or postfix form.
In browsers - The back button in a browser saves all the URLs you have visited
previously in a stack. Each time you visit a new page, it is added on top of the
stack. When you press the back button, the current URL is removed from the
stack, and the previous URL is accessed.
Advantages of Stack:
• Stacks are simple data structures with a well-defined set of operations, which
makes them easy to understand and use.
• Stacks are efficient for adding and removing elements, as these operations have a
time complexity of O(1).
• In order to reverse the order of elements we use the stack data structure.
• Stacks can be used to implement undo/redo functions in applications.
Drawbacks of Stack:
• Restriction of size in Stack is a drawback and if they are full, you cannot add any
more elements to the stack.
• Stacks do not provide fast access to elements other than the top element.
• Stacks do not support efficient searching, as you have to pop elements one by one
until you find the element you are looking for.
Stack Implementations in Python
• The most common stack implementation is using arrays, but it can also be
implemented using lists.
• Stack in Python can be implemented using the following ways:
• list
• Collections.deque
• queue.LifoQueue
Stack Implementation using list:
• Python’s built-in data structure list can be used as a stack. Instead of push(), append() is
used to add elements to the top of the stack while pop() removes the element in LIFO
order.
• Unfortunately, the list has a few shortcomings. The biggest issue is that it can run into
speed issues as it grows.
• The items in the list are stored next to each other in memory, if the stack grows bigger
than the block of memory that currently holds it, then Python needs to do some memory
allocations. This can lead to some append() calls taking much longer than other ones.
• For the array-based implementation of a stack, the push and pop operations take
constant time, i.e. O(1).
# Stack implementation in python
# Creating a stack
def create_stack():
stack = []
return stack
# Creating an empty stack
def check_empty(stack):
return len(stack) == 0
# Adding items into the stack
def push(stack, item):
stack.append(item)
print("pushed item: " + item)
# Removing an element from the stack
def pop(stack):
if (check_empty(stack)):
return "stack is empty"
return stack.pop()
stack = create_stack()
push(stack, str(1))
push(stack, str(2))
push(stack, str(3))
push(stack, str(4))
print("popped item: " + pop(stack))
print("stack after popping an element: " + str(stack))
Stack Implementation using list:
Output :-
Implementation using collections.deque:
• Python stack can be implemented using the deque class from the collections
module.
• Deque is preferred over the list in the cases where we need quicker append and
pop operations from both the ends of the container, as deque provides an O(1)
time complexity for append and pop operations as compared to list which
provides O(n) time complexity.
Implementation using collections.deque:
Implementation using collections.deque:
Implementation using queue module
Queue module also has a LIFO Queue, which is basically a Stack. Data is inserted into Queue using the
put() function and get() takes data out from the Queue.
Output: AB+CDE/*-F+
Input: A*B+C
Output: AB*C+
Input: (A+B)*(C/D)
Output: AB+CD/*
Input: A*(B*C+D*E)+F
Output: ABC*DE*+*F+
Input: (A+B)*C+(D-E)/F+G
Output: AB+C*DE-F/+G+
Why postfix representation of the expression?
• The compiler scans the expression either from left to right or from right to left.
Consider the expression: a + b * c + d
• The compiler first scans the expression to evaluate the expression b * c, then
again scans the expression to add a to it.
• The result is then added to d after another scan.
• The repeated scanning makes it very inefficient. Infix expressions are easily
readable and solvable by humans whereas the computer cannot differentiate the
operators and parenthesis easily so, it is better to convert the expression to
postfix(or prefix) form before evaluation.
• The corresponding expression in postfix form is abc*+d+. The postfix
expressions can be evaluated easily using a stack.
How to convert an Infix expression to a Postfix expression?
(Reverse Polish Notation)
• Scan all the symbols one by one from left to right in the given Infix
Expression.
• If the reading symbol is an operand, then immediately append it to the Postfix
Expression.
• If the reading symbol is left parenthesis ‘( ‘, then Push it onto the Stack.
• If the reading symbol is right parenthesis ‘)’, then Pop all the contents of the
stack until the respective left parenthesis is popped and append each popped
symbol to Postfix Expression.
• If the reading symbol is an operator (+, –, *, /), then Push it onto the Stack.
However, first, pop the operators which are already on the stack that have
higher or equal precedence than the current operator and append them to the
postfix. If an open parenthesis is there on top of the stack then push the
operator into the stack.
• If the input is over, pop all the remaining symbols from the stack and append
them to the postfix.
Consider the following infix expression a convert into reverse polish notation
using stack.
Expression=A + (B*C -(D/E ^ F) * H)
Algorithm to evaluate Arithmetic expression
* : Operator, pop top two elements, op1 = 7, op2 = 3. Stack after pop operations S = [5],
top = 5. Now, we push the result of op1 * op2, i.e 7 * 3 = 21 into the stack. S = [5, 21],
top = 21
+ : Operator, pop top two elements, op1 = 21, op2 = 5. Stack after pop operations S = [].
Push the result of op1 + op2 into the stack, i.e 21 + 5 = 26, S = [26]
The string has been completely traversed, the stack contains only 1 element which is the
result of the expression = 26.
Programming Exercises
1.Valid Parentheses
Description: Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string
is valid.
Problem Link: Valid Parentheses
2. Min Stack
Description: Design a stack that supports push, pop, top, and retrieving the minimum element in constant
time.
Problem Link: Min Stack
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.
The steps to follow are −
IF disk == 1, THEN
move disk from source to dest
ELSE
Hanoi(disk - 1, source, aux, dest) // Step 1
move disk from source to dest // Step 2
Hanoi(disk - 1, aux, dest, source) // Step 3
END IF
END Procedure
STOPrithm for Tower of Hanoi can be driven as follows −
Implement tower of hanoi using stack
• The Tower of Hanoi is a classic puzzle game that involves three poles and a number of
disks of different sizes. The goal of the game is to move all the disks from one pole to
another pole while following the rules:
• 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.
• The problem of moving the entire tower from one pole to another pole is solved
recursively. However, in this implementation, we will use an iterative approach and a
stack data structure to simulate the recursive steps.
• Tower 1 (source pole): 4 → 3 → 2 → 1
• Tower 2 (auxiliary pole): empty
• Tower 3 (destination pole): empty
• The goal is to move all the disks from Tower 1 to Tower 3 while following the
rules mentioned above.
# Recursive Python function to solve tower of hanoi
• A Queue is defined as a linear data structure that is open at both ends and the
operations are performed in First In First Out (FIFO) order.
• A queue is a useful data structure in programming. It is similar to the ticket
queue outside a cinema hall, where the first person entering the queue is the
first person who gets the ticket.
• Queue follows the First In First Out (FIFO) rule - the item that goes in first
is the item that comes out first.
Queue Data Structure
In the above image, we can see initially queue was empty and when data is
inserted it is inserted at one end, while the data is removed from other end.
since 1 was kept in the queue before 2, it is the first to be removed from the
queue as well. It follows the FIFO rule.
1. Simple Queue : In a simple queue, insertion takes place at the rear and removal occurs at
the front. It strictly follows the FIFO (First in First out) rule.
2. Circular Queue: In a circular queue, the last element points to the first element making a
circular link.
The main advantage of a circular queue over a simple queue is better memory utilization.
If the last position is full and the first position is empty, we can insert an element in the
first position. This action is not possible in a simple queue.
3. Priority Queue : A priority queue is a special type of queue in which each element is
associated with a priority and is served according to its priority. If elements with the
same priority occur, they are served according to their order in the queue.
4. Deque (Double Ended Queue) : In a double ended queue, insertion and removal of
elements can be performed from either from the front or rear. Thus, it does not follow the
FIFO (First In First Out) rule.
Basic Operations of Queue
A queue is an object (an abstract data structure - ADT) that allows the following
operations:
• We usually use arrays to implement queues in Java and C/++. In the case of
Python, we use lists.
Output :-
Implementation using collections.deque
• Queue in Python can be implemented using deque class from the collections
module.
• Deque is preferred over list in the cases where we need quicker append and
pop operations from both the ends of container, as deque provides an O(1) time
complexity for append and pop operations as compared to list which provides
O(n) time complexity.
• Instead of enqueue and deque, append() and popleft() functions are used.
Implementation using collections.deque
Implementation using collections.deque
Implementation using queue.Queue
• Queue is built-in module of Python which is used to implement a queue.
• queue.Queue(maxsize) initializes a variable to a maximum size of maxsize. A maxsize
of zero ‘0’ means a infinite queue. This Queue follows FIFO rule.
• And we can only add indexes 0 and 1 only when the queue is reset (when all
the elements have been dequeued).
• After REAR reaches the last index, if we can store extra elements in the empty
spaces (0 and 1), we can make use of the empty spaces. This is implemented by
a modified queue called the circular queue.
Circular Queue Data Structure
• A circular queue is the extended version of a regular queue where the last element is
connected to the first element. Thus forming a circle-like structure.
• The circular queue solves the major limitation of the normal queue. In a normal
queue, after a bit of insertion and deletion, there will be non-usable empty space.
• Circular Queue works by the process of circular increment i.e. when we try to
Circular Queue Operations
• The complexity of the enqueue and dequeue operations of a circular queue is O(1) for (array
implementations).
Enqueue Operation
check if the queue is full
•for the first element, set value of FRONT to 0
•circularly increase the REAR index by 1 (i.e. if the rear reaches the end,
next it would be at the start of the queue)
•add the new element in the position pointed to by REAR
Dequeue Operation
However, the check for full queue has a new additional case:
The second case happens when REAR starts from 0 due to circular increment and
when its value is just 1 less than FRONT, the queue is full.
Circular Queue Implementations in Python
Circular Queue Implementations in Python
Output
Programming Exercises