Dsa (Week 5)

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 67

DSA (WEEK 5)

Stacks and Queue


Stack
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.

You can think of the stack data structure as the pile of plates on top of another.

Here, you can:


Put a new plate on top
Remove the top plate

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.

Push: Add an element to the top of a stack

Pop: Remove an element from the top of a stack

IsEmpty: Check if the stack is empty

IsFull: Check if the stack is full

Peek: Get the value of the top element without removing it


Working of Stack Data Structure

The operations work as follows:

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.

5. Before pushing, we check if the stack is already full

6. Before popping, we check if the stack is already empty


Applications of Stack Data Structure
• Although stack is a simple data structure to implement, it is very powerful. The
most common uses of a stack are:

 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.

There are various functions available in this module:


 maxsize – Number of items allowed in the queue.
 empty() – Return True if the queue is empty, False otherwise.
 full() – Return True if there are maxsize items in the queue. If the queue was initialized with
maxsize=0 (the default), then full() never returns True.
 get() – Remove and return an item from the queue. If the queue is empty, wait until an item is
available.
 get_nowait() – Return an item if one is immediately available, else raise QueueEmpty.
 put(item) – Put an item into the queue. If the queue is full, wait until a free slot is available before
adding the item.
 put_nowait(item) – Put an item into the queue without blocking. If no free slot is immediately
available, raise QueueFull.
 qsize() – Return the number of items in the queue.
Implementation using queue module
Implementation using queue module
Convert Infix expression to Postfix expression

Infix expression: The expression of the form “a operator b” (a + b) i.e., when an


operator is in-between every pair of operands.
Postfix expression: The expression of the form “a b operator” (ab+) i.e., When
every pair of operands is followed by an operator.
Input: A + B * C + D
Output: ABC*+D+

Input: ((A + B) – C * (D / E)) + F

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

To evaluate an infix expression, We need to perform 2 main tasks:


Convert infix to postfix
Evaluate postfix
Traverse the expression:
1.1 If the character is an operand, push it into the stack.
1.2 If the character is an operator, pop the 2 top most elements from the stack
and perform the operation. Push the result back to the stack.
Once the expression is fully traversed, the element in the stack is the result.
Given expression is: 5 + 3 * 7.

Step 1 is to change this infix expression to postfix: 5 3 7 * +

Step 2: Stack S = [], traverse the string:

5 : Operand, push into the stack, S = [5], top = 5

3 : Operand, push into the stack, S = [5, 3], top = 3

7 : Operand, push into the stack, S = [5, 3, 7], top = 7

* : 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

3. Evaluate Reverse Polish Notation


Description: Evaluate the value of an arithmetic expression in Reverse Polish Notation. Problem Link:
Evaluate Reverse Polish Notation
1.Valid Parentheses
2. Min Stack
Evaluate Reverse Polish Notation
Tower of Hanoi: Recursive Algorithm
Tower of Hanoi puzzle with n disks can be solved in minimum 2 n−1 steps. This
presentation shows that a puzzle with 3 disks has taken 2 3 - 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.
The 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 algoSTART
Procedure Hanoi(disk, source, dest, aux)

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

def TowerOfHanoi(n, from_rod, to_rod, aux_rod):


if n == 0:
return
TowerOfHanoi(n-1, from_rod, aux_rod, to_rod)
print("Move disk", n, "from rod", from_rod, "to rod", to_rod)
TowerOfHanoi(n-1, aux_rod, to_rod, from_rod)
# Driver code
N=3
# A, C, B are the name of rods
TowerOfHanoi(N, 'A', 'C', 'B')
Queue Data Structure

• 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.

In programming terms, putting items in the queue is called enqueue, and


removing items from the queue is called dequeue.
Applications of Queue

• CPU scheduling, Disk Scheduling


• When data is transferred asynchronously between two processes.The queue is
used for synchronization. For example: IO Buffers, pipes, file IO, etc
• Handling of interrupts in real-time systems.
• Call Center phone systems use Queues to hold people calling them in order.
Types of Queues
There are four different types of queues:

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:

• Enqueue: Add an element to the end of the queue


• Dequeue: Remove an element from the front of the queue
• IsEmpty: Check if the queue is empty
• IsFull: Check if the queue is full
• Peek: Get the value of the front of the queue without removing it
Working of Queue

• Queue operations work as follows:


• two pointers FRONT and REAR
• FRONT track the first element of the queue
• REAR track the last element of the queue
• initially, set value of FRONT and REAR to -1
Enqueue Operation
Enqueue Operation
• check if the queue is full
• for the first element, set the value of FRONT to 0
• increase the REAR index by 1
• add the new element in the position pointed to by REAR
Dequeue Operation
Dequeue Operation
• check if the queue is empty
• return the value pointed by FRONT
• increase the FRONT index by 1
• for the last element, reset the values of FRONT and REAR to -1
Queue Implementations in Python

• We usually use arrays to implement queues in Java and C/++. In the case of
Python, we use lists.

• Queue in Python can be implemented by the following ways:


• list
• collections.deque
• queue.Queue
Implementation using list

• List is a Python’s built-in data structure that can be used as a queue.


• Instead of enqueue() and dequeue(), append() and pop() function is used.
• However, lists are quite slow for this purpose because inserting or deleting
an element at the beginning requires shifting all of the other elements by
one.
• The complexity of enqueue and dequeue operations in a queue using an
array is O(1).
• If you use pop(N) in python code, then the complexity might be O(n)
depending on the position of the item to be popped.
Implementation using list
Implementation using list

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.

• There are various functions available in this module:


• maxsize – Number of items allowed in the queue.
• empty() – Return True if the queue is empty, False otherwise.
• full() – Return True if there are maxsize items in the queue. If the queue was initialized
with maxsize=0 (the default), then full() never returns True.
• get() – Remove and return an item from the queue. If queue is empty, wait until an item
is available.
• get_nowait() – Return an item if one is immediately available, else raise QueueEmpty.
• put(item) – Put an item into the queue. If the queue is full, wait until a free slot is
available before adding the item.
• put_nowait(item) – Put an item into the queue without blocking. If no free slot is
immediately available, raise QueueFull.
• qsize() – Return the number of items in the queue.
Implementation using queue.Queue
Implementation using queue.Queue
Limitations of Queue
• As you can see in the image below, after a bit of enqueuing and dequeuing, the
size of the queue has been reduced.

• 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 circular queue work as follows:


• two pointers FRONT and REAR
• FRONT track the first element of the queue
• REAR track the last elements of the queue
• initially, set value of FRONT and REAR to -1

• 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

• check if the queue is empty


• return the value pointed by FRONT
• circularly increase the FRONT index by 1
• for the last element, reset the values of FRONT and REAR to -1

However, the check for full queue has a new additional case:

• Case 1: FRONT = 0 && REAR == SIZE - 1


• Case 2: FRONT = REAR + 1

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

1.Implement Queue using Stacks


Description: Implement a first-in-first-out (FIFO) queue using only two
stacks.
Problem Link: Implement Queue using Stacks

2. Number of Recent Calls


Description: Write a class RecentCounter to count recent requests. It has only
one method: ping(int t), where t represents the time in milliseconds.
Problem Link: Number of Recent Calls

3. Design Circular Queue Description:


Design your implementation of a circular queue. The circular queue is a linear
data structure in which the operations are performed based on FIFO (First In
First Out) principle.
Problem Link: Design Circular Queue
1.Implement Queue using Stacks
Number of Recent Calls

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy