0% found this document useful (0 votes)
29 views53 pages

DSA1 Questions

The document discusses time complexities of merge sort and quick sort algorithms. Merge sort has a time complexity of O(n log n) in all cases. Quick sort has a best case time complexity of O(n log n) but a worst case time complexity of O(n^2).

Uploaded by

Saloni Vani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
29 views53 pages

DSA1 Questions

The document discusses time complexities of merge sort and quick sort algorithms. Merge sort has a time complexity of O(n log n) in all cases. Quick sort has a best case time complexity of O(n log n) but a worst case time complexity of O(n^2).

Uploaded by

Saloni Vani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 53

Q.

1 FIND OUT THE TIME COMPLEXITY OF MERGE SORT


AND QUICK SORT
==> Merge Sort
Merge sort is defined as a sorting algorithm that works by dividing an array into smaller
subarrays, sorting each subarray, and then merging the sorted subarrays back together to form
the final sorted array.
In simple terms, we can say that the process of merge sort is to divide the array into two halves, sort
each half, and then merge the sorted halves back together. This process is repeated until the entire
array is sorted

Merge Sort is a recursive algorithm and time complexity can be expressed as


following recurrence relation. T(n) = 2T(n/2) + O(n) The solution of the above
recurrence is O(nLogn).

Time Complexity: O(n*log n) Average Time Complexity: O(n*log n) The time


complexity of MergeSort is O(n*Log n) in all the 3 cases (worst, average and best) as
the mergesort always divides the array into two halves and takes linear time to merge
two halves.
Best-case time complexity:
The best-case time complexity occurs when the input data is already sorted. In this
case, Merge Sort still divides the array into halves but doesn't perform many swaps or
comparisons because the merging step is straightforward.
Divide: O(log n)
Merge: O(n)
The best-case time complexity is O(log n) for the divide step plus O(n) for the merge
step, which simplifies to O(n log n) in total.
Average-case time complexity:
The average-case time complexity of Merge Sort is also O(n log n). This is a general
property of Merge Sort and is derived from the algorithm's design. It doesn't depend on
the specific distribution of the input data.
Worst-case time complexity:
The worst-case time complexity of Merge Sort is also O(n log n). This occurs when
the input data is in reverse order or in a nearly reversed order. In such cases, the
algorithm still performs the same number of divide and merge operations, leading to
the same time complexity as the average case.
In summary:
Worst Case Time Complexity [ Big-O ]: O(n*log n)

Best Case Time Complexity [Big-omega]: O(n*log n)

Average Time Complexity [Big-theta]: O(n*log n)

Space Complexity: O(n)

Uhi hai smjh aajye to

==>Quick sort
Quick Sort is a sorting algorithm which uses divide and conquer technique.
In quick sort we choose an element as a pivot and we create a partition of array around that pivot.
by repeating this technique for each partition we get our array sorted

depending on the position of the pivot we can apply quick sort in different ways
 taking first or last element as pivot
 taking median element as pivot

Time Complexity

Case Time Complexity

Best Case O(n*logn)


Average Case O(n*logn)
Worst Case O(n2)

Best case Time Complexity of Quick Sort

O(Nlog(N))
In Quicksort, the best-case occurs when the pivot element is the middle element or near to the middle
element. The best-case time complexity of quicksort is O(n*logn).

In this case the recursion will look as shown in diagram, as we can see in diagram the height of
tree is logN and in each level we will be traversing to all the elements with total operations will
be logN * N
as we have selected mean element as pivot then the array will be divided in branches of equal
size so that the height of the tree will be mininum
pivot for each recurssion is represented using blue color
time complexity will be O(NlogN)
Explanation:-

(derivation hai jaruri lge to hi pdnaa)


2 Average-case time complexity:
o It occurs when the array elements are in jumbled order that is not properly ascending and not
properly descending. The average case time complexity of quicksort is O(n*logn).

The average-case time complexity of Quick Sort is typically O(n log n). This is
based on the assumption that the pivot selection is random or follows a good
strategy that leads to reasonably balanced partitions. In practice, Quick Sort tends to
perform well on average, even without perfect randomness.

Formula: T_avg(n) = c * n * log₂(n), where 'c' is a constant representing the number of


operations per element.

3 Worst-case time complexity:


In quick sort, worst case occurs when the pivot element is either greatest or smallest element. Suppose, if the
pivot element is always the last element of the array, the worst case would occur when the given array is
sorted already in ascending or descending order. The worst-case time complexity of quick sort is O(n2).
Formula: T_worst(n) = c * n^2, where 'c' is a constant representing the number of
operations per element.

O(N^2)

This will happen when we will when our array will be sorted and we select smallest or largest
indexed element as pivot
as we can see in diagram we are always selecting pivot as corner index elements
so height of the tree will be n and in top node we will be doing N operations
then n-1 and so on till 1
*Stack Pseudocode:*
8. Write pseudocode to push an element onto a stack.
PUSH Operation
PUSH (S, TOP, X)
Step – 1: [Check for Stack overflow]
If TOP > N then
Write (‘Stack Overflow’)
Return
Step – 2 : [Increase value of TOP]
TOP <- TOP + 1
Step – 3 : [Insert element into stack]
S[Top] <- X
Step – 4: [Finished]
Return

9. How do you pop(delete) an element from a stack using


pseudocode?
POP(S, TOP)
Step – 1: [Check for Stack Underflow]
If TOP == 0 then
Write (‘Stack Underflow’)
Return
Step – 2 : [Decrease value of TOP]
TOP <- TOP – 1
Step – 3 : [Return top element of stack]
Return S[Top + 1]

10. Explain the concept of a LIFO data structure.

It is a Last-In-First-Out (LIFO) data structure, which means that the last element
added to the stack is the first element to be removed. This is similar to a stack of plates,
where the last plate added is the first plate to be removed. The basic operations that can
be performed on a stack include push, pop, peek, and is_empty.
The push operation is used to add an element to the top of the stack.
The pop operation is used to remove the top element from the stack.
The peek operation is used to view the top element without removing it.
The is_empty operation is used to check if the stack is empty.
Characteristics of LIFO

 The LIFO principle is very effective in implementing Stack. It is a linear data


structure.
 You can both- add and remove elements from the same end.
 The end in LIFO is known as the top.
 The utilization of memory varies with each operation. So, the memory consumed
has no fixed amount.
 It requires no fixed size for consuming memories.
 The LIFO approach comes into play in computing as a queuing theory. It refers
to how various data structures store the items.

General Operations
One can perform these general operations on a Stack data structure:

 Push operation – This term means that you can insert an element at the top of
the stack.
 Peek operation – It means that you can return the topmost element without
having to delete it from the stack.
 Pop operation – This term means you can remove the element from the top of
the stack.

Where To Use LIFO?

 Data Structures – Some data structures preferably use the LIFO approach for
processing data. Such structures include Stacks and their other variants.
PsuedoCode for implementing
#include <iostream>
#include <stack>

int main() {
// Create a stack to represent the LIFO structure
std::stack<int> lifoStack;

// Push elements onto the stack (Last-In)


lifoStack.push(1);
lifoStack.push(2);
lifoStack.push(3);

// Pop elements from the stack (First-Out)


while (!lifoStack.empty()) {
int topElement = lifoStack.top(); // Get the top element
lifoStack.pop(); // Remove the top element
std::cout << "Popped: " << topElement << std::endl;
}

return 0;
}

11. Write pseudocode for checking if a stack is empty.


isEmpty()

The isEmpty() operation verifies whether the stack is empty. This operation is used to
check the status of the stack with the help of top pointer.

Algorithm

1. START
2. If the top value is -1, the stack is empty. Return 1.
3. Otherwise, return 0.
4. END

int isempty() {
if(top == -1)
return 1;
else
return 0;
}
12. Can you implement a stack using an array? Provide pseudocode.
Implementing Stack using an Array
To implement stack using array we need an array of required size and top pointer to
insert/delete data from the stack and by default top=-1 i.e the stack is empty.

In array implementation, the stack is formed by using the array. All the operations
regarding the stack are performed using arrays. Lets see how each operation can be
implemented on the stack using array data structure.

push(value) - Inserting value into the stack

 Step 1 - Check whether stack is FULL. (top == SIZE-1)


 Step 2 - If it is FULL, then display "Stack is FULL!!! Insertion is not
possible!!!" and terminate the function.
 Step 3 - If it is NOT FULL, then increment top value by one (top++) and set
stack[top] to value (stack[top] = value).
pop() - Delete a value from the Stack

 Step 1 - Check whether stack is EMPTY. (top == -1)


 Step 2 - If it is EMPTY, then display "Stack is EMPTY!!! Deletion is not
possible!!!" and terminate the function.
 Step 3 - If it is NOT EMPTY, then delete stack[top] and decrement top value
by one (top--).

13. Explain the purpose of the "top" pointer in a stack.


(:-top pointer: stores the address of first element of stack)

A stack is an Abstract Data Type (ADT), that is popularly used in most programming
languages. It is named stack because it has the similar operations as the real-world
stacks, for example – a pack of cards or a pile of plates, etc.

The stack follows the LIFO (Last in - First out) structure where the last element
inserted would be the first element deleted.

In a stack data structure, the "top pointer" is typically a reference or a variable that
points to the top element of the stack. You can use it to push elements onto the stack
and pop elements from it. Here's a pseudocode representation of a stack with a "top
pointer":

Psuedocode:

Stack with Top Pointer:

Initialize an empty stack (e.g., an array or linked list) and a top pointer (an integer).

Push(element):

if stack is full:

return "Stack Overflow"

else:

increment the top pointer

stack[top] = element
Pop():

if stack is empty:

return "Stack Underflow"

else:

element = stack[top]

decrement the top pointer

return element

Top():

if stack is empty:

return "Stack is empty"

else:

return stack[top]

IsEmpty():

return (top == -1)

IsFull():

return (top == MAX_SIZE - 1)

In this pseudocode:

Push(element) pushes an element onto the stack by incrementing the top pointer and
placing the element at that position. It checks for stack overflow (i.e., if the stack is
already full).
Pop() pops an element from the stack by returning the element at the top pointer
position and then decrementing the top pointer. It checks for stack underflow (i.e., if
the stack is empty).
Top() returns the element at the top of the stack without removing it. It checks if the
stack is empty before attempting to access the top element
IsEmpty() checks if the stack is empty by comparing the top pointer with -1.
IsFull() checks if the stack is full by comparing the top pointer with the maximum size
of the stack (MAX_SIZE - 1).

*Infix to Prefix Conversion:*


Que 15- What is the algorithm for converting an infix expression to
a prefix expression?
Converting an infix expression to a prefix expression involves rearranging the
operators and operands while maintaining the correct order of operations. To do this,
you can use a stack data structure to help with the conversion. Here's a step-by-step
pseudocode algorithm to convert an infix expression to a prefix expression:
Given an infix expression, the task is to convert it to a prefix expression.
Infix Expression: The expression of type a ‘operator’ b (a+b, where +
is an operator) i.e., when the operator is between two operands.
Prefix Expression: The expression of type ‘operator’ a b (+ab where +
is an operator) i.e., when the operator is placed before the operands.
To convert an infix expression to a prefix expression, we can use
the stack data structure. The idea is as follows:
Algorithm:
Reverse the input infix expression.
Scan the reversed infix expression from left to right.
Initialize an empty stack to hold operators and output queue (or list) to store the
converted prefix expression.
For each character in the reversed infix expression:
a. If it is an operand (a letter, digit, etc.), add it to the output queue.
b. If it is an operator or parenthesis, do the following:
i. If it's an opening parenthesis '(', push it onto the stack.
ii. If it's a closing parenthesis ')', pop operators from the stack and add them to
the output queue until an opening parenthesis '(' is encountered. Pop and discard
the opening parenthesis.
iii. If it's an operator, pop operators from the stack and add them to the output
queue if they have higher precedence than the current operator. Push the current
operator onto the stack.
After scanning the entire expression, pop any remaining operators from the stack
and add them to the output queue.
Reverse the output queue to obtain the final prefix expression.
Q.16 Write ALGORITHM for converting "A + B" to its
prefix form.
I how to convert "A + B" to its prefix form. To convert "A + B" to its prefix form, we
need to first move the operator '+' to the left of the operands 'A' and 'B'. This means the
operator will become the prefix of the expression and the operands will follow. So, the
prefix form of "A + B" will be "+AB". Therefore, the algorithm for converting "A +
B" to its prefix form is:
Start
2. Read the expression "A + B"
3. Move the operator '+' to the left of the operands 'A' and 'B'
4. Replace "A + B" with "+AB"
5. End I hope this explanation helps you.

Q.17 How do you handle operator precedence in infix to prefix


conversion?
According to priority..(nikal liya na tune)…
Operator Precedence:
^ (Exponentiation)
* / (Multiplication and Division)
+ - (Addition and Subtraction)

*Infix to Postfix Conversion:*


18. Explain the algorithm for converting an infix expression to a
postfix expression.
An infix and postfix are the expressions. An expression consists of constants, variables,
and symbols. Symbols can be operators or parenthesis. All these components must be
arranged according to a set of rules so that all these expressions can be evaluated using
the set of rules.

Here, we will use the stack data structure for the conversion of infix expression to
prefix expression. Whenever an operator will encounter, we push operator into the
stack. If we encounter an operand, then we append the operand to the expression.
Rules for the conversion from infix to postfix expression

Initialize an empty stack for operators and an empty output queue (or list) for the
postfix expression.

Define a precedence order for operators. This defines which operators have higher
precedence than others. For example:

^ (Exponentiation) > * and / (Multiplication and Division) > + and - (Addition and
Subtraction)

Start scanning the infix expression from left to right, one character at a time.

For each character in the infix expression:

a. If it's an operand (a letter, digit, etc.), add it to the output queue.

b. If it's an operator:

i. While the stack is not empty and the operator on top of the stack has higher or
equal precedence than the current operator, pop operators from the stack and add
them to the output queue.

ii. Push the current operator onto the stack.

If you encounter an opening parenthesis '(', push it onto the stack.

If you encounter a closing parenthesis ')', pop operators from the stack and add
them to the output queue until you find the matching opening parenthesis '('. Pop
and discard the opening parenthesis.

After scanning the entire infix expression, pop any remaining operators from the
stack and add them to the output queue.

The output queue now contains the postfix expression.

Let's go through an example of converting an infix expression to a postfix


expression step by step. We'll use the following infix expression:

Infix Expression: (A + B) * C - D / E

We'll follow the algorithm mentioned earlier to convert it to postfix notation:

Initialize an empty stack and an empty output queue.


Start scanning the infix expression from left to right.

Character '(': It's an opening parenthesis, so push it onto the stack.

Operand 'A': Add it to the output queue.

Operator '+': Since the stack is empty, push '+' onto the stack.

Stack: +

Output Queue: A

Operand 'B': Add it to the output queue.

Character ')': Pop operators from the stack and add them to the output queue until
we encounter the opening parenthesis '('.

Stack: (empty)

Output Queue: AB+

Operator '': Push '' onto the stack.

Stack: *

Output Queue: AB+

Operand 'C': Add it to the output queue.

Stack: *

Output Queue: AB+C

Operator '-': Pop operators from the stack (which only contains '*') and add them to
the output queue. Then, push '-' onto the stack.

Stack: -

Output Queue: AB+C*

Operand 'D': Add it to the output queue.

Stack: -

Output Queue: AB+C*D


Operator '/': Pop operators from the stack (which contains '-') and add them to the
output queue. Then, push '/' onto the stack.

Stack: /

Output Queue: AB+C*D-

Operand 'E': Add it to the output queue.

Stack: /

Output Queue: AB+C*D-E

After scanning the entire expression, pop any remaining operators from the stack
and add them to the output queue.

Stack: (empty)

Output Queue: AB+C*D-E/-

The output queue now contains the postfix expression:

Postfix Expression: AB+C*D-E/-

Que 19. Write ALGORITHM to convert "A * (B + C)" to its


postfix form.
To convert the infix expression "A * (B + C)" to its postfix (also known as Reverse
Polish Notation or RPN) form, you can use the shunting-yard algorithm, which is a
common method for this purpose. Here are the steps to convert the expression:

.Initialize an empty stack for operators and an empty list for the output.

.Start scanning the expression from left to right.

.For each character in the expression:

a. If it's an operand (in this case, A, B, or C), add it to the output list.
b. b. If it's an operator (+ or *), pop operators from the stack and add them to the
output list until you find an operator with lower precedence or an open parenthesis '('.
Then push the current operator onto the stack.
c. If it's an open parenthesis '(', push it onto the stack. d. If it's a closing parenthesis ')',
pop operators from the stack and add them to the output list until an open parenthesis
'(' is encountered. Pop and discard the open parenthesis.
.After scanning the entire expression, pop any remaining operators from the stack and
add them to the output list.

Here's the conversion of "A * (B + C)" to its postfix form:

Input: A * (B + C)

Output (Postfix): A B C + *

So, "A * (B + C)" in postfix notation is "A B C + *".

Que. 20 How do you handle parentheses in infix to postfix


conversion?
To handle parentheses in infix to postfix conversion, you can use a stack data structure.
Here's a step-by-step guide on how to do it:

Initialize an empty stack to hold operators.

Initialize an empty list to store the postfix expression.

Scan the infix expression from left to right, one character at a time.

For each character in the infix expression:

a. If it's an operand (a number or a variable), add it to the postfix list.

b. If it's an open parenthesis '(', push it onto the stack.

c. If it's a closing parenthesis ')', pop operators from the stack and append them to the
postfix list until an open parenthesis '(' is encountered. Pop and discard the open
parenthesis as well.

d. If it's an operator (+, -, *, /, etc.), compare its precedence with the operator at the top
of the stack. Pop and append operators from the stack to the postfix list until the stack
is empty or the top operator has lower precedence than the current operator, then push
the current operator onto the stack.

After scanning the entire infix expression, if there are any operators left in the stack,
pop and append them to the postfix list.

The postfix list now contains the equivalent postfix expression.

*Postfix to Infix Conversion:*

21. Describe the algorithm for converting a postfix expression to an infix


expression.
Some important terminology
Postfix Expression
In Postfix Expression operator appear after operand, this expression is known as Postfix
operation.
Infix
If Infix Expression operator is in between of operands, this expression is known as Infix
operation.

Steps to Convert Postfix to Infix


 Start Iterating the given Postfix Expression from Left to right
 If Character is operand then push it into the stack.
 If Character is operator then pop top 2 Characters which is operands from the stack.
 After poping create a string in which comming operator will be in between the operands.
 push this newly created string into stack.
 Above process will continue till expression have characters left
 At the end only one value will remain if there is integers in expressions. If there is
character then one string will be in output as infix expression.

Example to convert postfix to Infix(srf smjhne ke liye haii


que me nhi hai)
Postfix Expression : abc-+de-+

Token Stack Action

a a push a in stack

b a, b push b in stack

c a, b, c push c in stack

pop b and c from stack and put – in between and


– a,b–c
push into stack

pop a and b-c from stack and put + in between and


+ a+b–c
push into stack

d a + b – c, d push d in stack

a + b – c, d ,
e push e in stack
e

a + b – c, d pop d and e from stack and put – in between and



–e push into stack

a + b – c + d pop a + b – c and d – e from stack and put + in


+
–e between and push into stack

23. How does the order of operands affect postfix to infix conversion?
In postfix to infix conversion, the order of operands indeed affects the resulting infix
expression. The order in which operands appear in the original postfix expression
determines the order in which they will be placed in the infix expression. Let me
clarify this:

In postfix notation (also known as Reverse Polish Notation or RPN), operands are
followed by operators, and the order of operands and operators is significant. When
converting from postfix to infix, you must maintain the correct order of operands as
they appeared in the original postfix expression.

Here's a simple example to illustrate this:

Postfix Expression: ABC*+


To convert this postfix expression to infix, you would follow these steps:

==>Start from the left of the postfix expression.


Take the first operand: A.
Take the second operand: B.
Take the operator: *.
Combine the operands and the operator to form the subexpression: (A * B).
Now, the expression becomes:

Infix Expression: (A * B) + C

As you can see, the order of operands (A, B, and C) in the original postfix expression
(ABC*+) directly affects the resulting infix expression ((A * B) + C).

So, the order of operands in the postfix expression plays a crucial role in determining
the correct infix expression during postfix to infix conversion.

*Prefix to Infix Conversion:*


24. Explain the process of converting a prefix expression to an infix
expression.
Infix : An expression is called the Infix expression if the operator appears in between
the operands in the expression. Simply of the form (operand1 operator operand2).
Example : (A+B) * (C-D)
Prefix : An expression is called the prefix expression if the operator appears in the
expression before the operands. Simply of the form (operator operand1 operand2).
Example : *+AB-CD (Infix : (A+B) * (C-D) )
Algorithm for Prefix to Infix:
1. Read the Prefix expression in reverse order (from right to left)
2. If the symbol is an operand, then push it onto the Stack
3. If the symbol is an operator, then pop two operands from the Stack
4. Create a string by concatenating the two operands and the operator between them.
string = (operand1 + operator + operand2)
And push the resultant string back to Stack
5. Repeat the above steps until the end of Prefix expression.
6. At the end stack will have only 1 string i.e resultant string.

Algorithm for Prefix to Infix: (thoda bda hai dekhna upr vala smjh
aa jaye to vo nhi to ye dekhna)
1. Start from the end of the prefix expression and scan it from right to left.
2. Initialize an empty stack to store operators.
3. For each character in the prefix expression:
a. If the character is an operand (a variable or constant), push it onto the stack as a
single-character string.
b. If the character is an operator (+, -, *, /, etc.), pop the top two operands from the
stack. These operands represent the right and left operands of the current operator.
c. Construct a subexpression by placing the operator between the two operands and
enclosing the subexpression in parentheses. For example, if you have the operator "+"
and operands "A" and "B," you would create the subexpression "(A + B)".
d. Push the subexpression back onto the stack as a string.
Continue scanning the entire prefix expression in this manner.
Once you have processed the entire prefix expression, the final result will be on top of
the stack.
Pop the result from the stack to obtain the fully converted infix expression.

25.CONVERT "+*ABC" from prefix to infix.


Here's how to convert the prefix expression "+*ABC" to infix:

1. Start from the end of the prefix expression: "C B * A +"


2. Initialize an empty stack.
3. Process the expression from right to left:
a. "C" is an operand, so push it onto the stack.
b. "B" is an operand, so push it onto the stack.
c. "*" is an operator. Pop the top two operands ("B" and "C"), create a
subexpression "(B * C)," and push it back onto the stack.
d. "A" is an operand, so push it onto the stack.
e. "+" is an operator. Pop the top two operands ("A" and "(B * C)"), create a
subexpression "(A + (B * C))," and push it back onto the stack.

After processing the entire prefix expression, the stack contains the fully converted
infix expression: "(A + (B * C))".
Pop the result from the stack, and you have the final infix expression: "A + (B * C)".
So, the infix expression corresponding to the prefix expression "+*ABC" is "A + (B *
C)".
*Tower of Hanoi Pseudocode:*
27. What is the Tower of Hanoi problem, and how is it solved using
recursion?
The Tower of Hanoi is also known as the Tower of Brahma or the Lucas Tower. It is a
mathematical game or puzzle that consists of three rods with ’n’ number of disks of
different diameters.

The objective of the game is to shift the entire stack of disks from one rod to another
rod following these three rules .

1. Only one disk can be moved at a time.

2. Only the uppermost disk from one stack can be moved on to the top of another
stack or an empty rod.

3. Larger disks cannot be placed on the top of smaller disks.

The minimal number of moves required to solve the Tower of Hanoi


puzzle of n disks would be (2^n) − 1.

The logic behind solving the Tower of Hanoi for three disks :

Objective : To solve the Tower of Hanoi puzzle that contains three disks. The stack of
disks has to be shifted from Rod 1 to Rod 3 by abiding to the set of rules that has been
mentioned above.
Step 1 : The smallest green disk , the uppermost disk on the stack is shifted from rod 1
to rod 3.

Step 2 : Next the uppermost disk on rod 1 is the blue colored disk which is shifted to
rod 2.

Step 3 : The smallest disk placed on rod 3 is shifted back on to the top of rod 2.

Step 4 : So now the largest red disk is allowed to be shifted from rod 1 to its
destination rod 3.
Step 5 : Now the two disks on rod 2 has to be shifted to its destination rod 3 on top of
the red disk , so first the smallest green disk on top of the blue rod is shifted to rod 1 .

Step 6 : Next the blue disk is permitted to be shifted to its destination rod 3 that will
stacked on to the top of the red disk.

Step 7 : Finally , the smallest green colored rod is also shifted to rod 3 , which would
now be the uppermost rod on the stack .
So the Tower of Hanoi for three disks has been solved !!

Solving the Tower of Hanoi program using recursion:


To solve the Tower of Hanoi problem using recursion, we can break it down into
smaller subproblems.
TowerOfHanoi function takes four parameters: n (the number of disks), source (the
peg from which to move the disks), target (the peg to which the disks should be
moved), and auxiliary (the temporary peg used for storage during the moves).
The base case of the recursion occurs when n is 0, in which case no moves need to be
made. Otherwise, the recursive steps involve:

1. Moving n-1 disks from the source peg to the auxiliary peg using the target peg as
temporary storage.
2. Moving the nth disk from the source peg to the target peg.
3. Moving the n-1disks from the auxiliary peg to the target peg using the source
peg as temporary storage.

Psuedocode:

procedure TowerOfHanoi(n, source, auxiliary, target):

if n == 1:

// Move a single disk from source to target

moveDisk(source, target)

else:

// Move n-1 disks from source to auxiliary using target as the auxiliary peg

TowerOfHanoi(n-1, source, target, auxiliary)

// Move the largest disk from source to target


moveDisk(source, target)

// Move the n-1 disks from auxiliary to target using source as the auxiliary peg

TowerOfHanoi(n-1, auxiliary, source, target)

procedure moveDisk(source, target):

// Print or perform the actual move operation here

print("Move a disk from " + source + " to " + target)

In this pseudocode:

n represents the number of disks to be moved.

source, auxiliary, and target represent the three pegs or towers.

The moveDisk procedure represents the actual operation of moving a disk from the
source peg to the target peg (you can replace this with your own implementation,
such as printing the move).

The TowerOfHanoi procedure is a recursive function that solves the Tower of


Hanoi problem for n disks by recursively moving smaller subproblems of n-1 disks.

The algorithm follows these steps:

If there is only one disk to move (n == 1), move it directly from the source peg to
the target peg.

If there are more than one disk (n > 1), recursively move n-1 disks from the source
peg to the auxiliary peg using the target peg as the auxiliary.

Move the largest disk from the source peg to the target peg.

Recursively move the n-1 disks from the auxiliary peg to the target peg using the
source peg as the auxiliary.

This recursive approach ensures that the Tower of Hanoi problem is solved
correctly for any number of disks.

28. Write pseudocode for solving the Tower of Hanoi problem with
three pegs and N disks.
Certainly, here's the pseudocode for solving the Tower of Hanoi problem with three
pegs (A, B, and C) and N disks:
Input: 3
Output: Disk 1 moved from A to C
Disk 2 moved from A to B
Disk 1 moved from C to B
Disk 3 moved from A to C
Disk 1 moved from B to A
Disk 2 moved from B to C
Disk 1 moved from A to C

Psuedocode:

#include <iostream>

void towerOfHanoi(int n, char source, char auxiliary, char destination) {

if (n == 1) {

// Base case: Move the top disk from the source peg to the destination peg

cout << "Move disk 1 from " << source << " to " << destination << std::endl;

return;

// Move n-1 disks from source to auxiliary peg using destination as a helper peg

towerOfHanoi(n - 1, source, destination, auxiliary);

// Move the remaining disk from source to destination peg

cout << "Move disk " << n << " from " << source << " to " << destination << endl;

// Move the n-1 disks from auxiliary to destination peg using source as a helper peg

towerOfHanoi(n - 1, auxiliary, source, destination);

int main() {

int n = 3; // Number of disks

towerOfHanoi(n, 'A', 'B', 'C'); // Calling the function with three pegs: A, B, and C

return 0;

}
In this C++ code:

towerOfHanoi is a recursive function that takes four arguments:

n: The number of disks to move.


source: The source peg.
auxiliary: The auxiliary (or intermediate) peg.
destination: The destination peg.
The base case occurs when n is 1, at which point it directly moves the top disk from
the source peg to the destination peg.

For n greater than 1, it follows the recursive algorithm to solve the Tower of Hanoi
problem, moving n-1 disks from the source peg to the auxiliary peg, moving the
remaining disk from the source peg to the destination peg, and finally moving the n-1
disks from the auxiliary peg to the destination peg using the source peg as a helper.

In the main function, you can change the value of n to the number of disks you want to
solve the problem for, and it will display the sequence of moves required to solve the
Tower of Hanoi problem.

*Queue Pseudocode:*
30. Write pseudocode for enqueueing an element in a queue.
Procedure Enqueue(queue, element):
# Create a new node with the given element
new_node = CreateNode(element)

# If the queue is empty, set both front and rear to the new node
if IsEmpty(queue):
queue.front = new_node
queue.rear = new_node
else:
# Otherwise, set the next pointer of the current rear node to the new node
queue.rear.next = new_node
# Update the rear pointer to the new node
queue.rear = new_node
# Helper function to create a new node
Function CreateNode(element):
new_node = new Node()
new_node.data = element
new_node.next = null
return new_node
# Helper function to check if the queue is empty
Function IsEmpty(queue):
return queue.front == null

Explanation:
1. The Enqueue procedure adds an element to the queue.
2. It first creates a new node with the given element.
3. If the queue is empty (both front and rear are null), it sets both front and rear to
the new node.
4. If the queue is not empty, it sets the next pointer of the current rear node to the new
node and updates the rear pointer to the new node.
5. The CreateNode function is a helper function to create a new node with the given
element.
6. The IsEmpty function is a helper function to check if the queue is empty, which is
determined by whether the front pointer is null.

=This pseudocode demonstrates a basic implementation of enqueueing an element in a


queue using a linked list. Depending on your programming language, you may need to
adapt the pseudocode to the syntax and data structures available in that language.

31. How do you dequeue an element from a queue using pseudocode?


Dequeueing (removing) an element from a queue is a fundamental operation. Here's
pseudocode to dequeue an element from a queue:
Procedure Dequeue(queue):
if IsEmpty(queue):
# The queue is empty, nothing to dequeue
Print("Queue is empty. Cannot dequeue.")
return null
else:
# Get the data from the front node
dequeued_element = queue.front.data

# Move the front pointer to the next node


queue.front = queue.front.next

# If the queue is now empty, update the rear pointer as well


if queue.front == null:
queue.rear = null

return dequeued_element

# Helper function to check if the queue is empty


Function IsEmpty(queue):
return queue.front == null
Explanation:

The Dequeue procedure checks if the queue is empty using the IsEmpty helper
function.
If the queue is empty, it prints an error message and returns null (or a similar
indication depending on your language).
If the queue is not empty, it retrieves the data from the front node (the element to be
dequeued).
It then moves the front pointer to the next node in the queue, effectively removing the
front node.
If the queue becomes empty after dequeueing (i.e., the front pointer becomes null), it
also updates the rear pointer to null.
This pseudocode demonstrates how to dequeue an element from a queue implemented
using a linked list. Depending on the specific programming language and data
structures you are using, you may need to adapt the pseudocode accordingly.

32. Explain the concept of a FIFO data structure


FIFO stands for First In First Out, in which we will enter the data elements into the data structure; , here,
we treat the data elements with a fair chance; the element which has entered first will get the opportunity to
leave first. The other name we call First Come First Serve is implemented using the data structure named a
queue.
A FIFO (First-In, First-Out) data structure is a type of data storage and retrieval system where the first item
that is added (enqueued) to the structure is the first one to be removed (dequeued). It operates on the
principle that the order of insertion is the order of removal, resembling a line of people waiting for a service,
where the person who arrives first is served first.

FIFO Operations
FIFO needs to allow two fundamental operations:

 dequeue – It lets the system remove the first element from the container.
 enqueue – The system appends/adds elements to the end of the container.
FIFO approach is used in Data Structures
The Queue is a linear data structure based on the first-in, first-out (FIFO) technique, in
which data pieces are added to the Queue from the back end and deleted from the front
End. The first-in, first-out (FIFO) technique is commonly employed in network
bridges, switches, and routers. It's also referred to as a linear queue.
Linear Queue
A linear queue is a data structure based on the FIFO (First in, First Out) principle, in
which data is inserted from the back End and removed from the front End. A linear
queue is referred to as the "simple queue," and whenever we discuss queues, we are
referring to a linear queue by default. We'll go through these points in greater depth
later.
Linear Queue Operations
Following are mentioned the significant operations in a Linear Queue data structure:
Enqueue
1. This operation is used to add new data items to the Queue.
2. Using this action, data is inserted into the Queue in the order in which it was
received. The Queue will be continued until it reaches its maximum capacity.
3. When data cannot be added to the Queue after it reaches the endpoint, this is an
Overflow situation.
4. From the rear end, the en-queue process is carried out.
Dequeue
1. To delete a data element from the Queue, perform this operation.
2. That data element is deleted, which is enqueued first in the Queue, or the items
are popped in the same order as they were put, using the Dequeue method.
3. This procedure will remove data components from the Queue until it is empty.
When all of the items have been deleted, the deletion procedure is unable to
complete, which is referred to as an Underflow situation.
4. It is carried out on the front End.
Peek
This operation is used to find out the very first queue element, which will be served
first without dequeuing it.

Complexity Analysis
Below the the complexities of the above mentioned operations:
Enqueue (Insert):
Time complexity: O(1)
Explanation: As we maintain 2 pointers (Front and End), we simply insert at the End
and increment the end pointer.
Dequeue (Delete)
Time Complexity: O(1)
Explanation: As we maintain 2 pointers (Front and End), we simply increment the
FRONT pointer.
Peek:
Time Complexity: O(1)
Explanation: Here, we just print the element at the REAR Pointer, thus O(1)

Applications:
o The FIFO principle has great importance in the various processes scheduling algorithm, which the
operating system does to finalize the schedule of several processes one after another.
o One of the algorithms that CPU uses is FCFS, first come, first serve to give a fair chance to every
process, and it is implemented by using the queue data structure.

o It is also used in CPU scheduling and memory management


o The data structure, based on the FIFO principle, is the queue.
o In the queue data structure, we will perform the two main operations: first, Enqueue operation for
inserting the data elements into the queue, and another is Dequeue operation for removing the data
elements from the queue.

33. Write pseudocode to check if a queue is full.

To check if a queue is full, you typically need to know the maximum capacity of the
queue (i.e., the maximum number of elements it can hold) and compare it to the
current number of elements in the queue. Here's pseudocode to check if a queue is full:

The isFull() Operation


The isFull() operation verifies whether the stack is full.

Algorithm
1 – START
2 – If the count of queue elements equals the queue size, return true
3 – Otherwise, return false
4 – END

Function IsFull(queue):
if queue.current_size == queue.max_capacity:
return true
else:
return false

Explanation:

queue represents the queue data structure you are checking.


queue.current_size represents the current number of elements in the queue.
queue.max_capacity represents the maximum capacity of the queue.
The pseudocode checks if the current_size of the queue is equal to its max_capacity. If
they are equal, it means the queue is full, and the function returns true. Otherwise, it
returns false.

34. Explain the difference between a regular queue and a priority


queue.
The queue is a linear data structure that inserts elements from the back and removes
elements from the starting end of the queue.

A priority queue is an extended version of the normal queue with priorities for each
element.

Priority Queue:

A priority queue is a type of queue that arranges elements based on their priority values.
Elements with higher priority values are typically retrieved before elements with lower
priority values.
In a priority queue, each element has a priority value associated with it. When you add an
element to the queue, it is inserted in a position based on its priority value.
Properties of Priority Queue
So, a priority Queue is an extension of the queue with the following properties.
 Every item has a priority associated with it.
 An element with high priority is dequeued before an element with low priority.
 If two elements have the same priority, they are served according to their order in the
queue.

Difference between Priority Queue and Normal Queue

In a queue, the first-in-first-out rule is implemented whereas, in a priority queue, the values are
removed on the basis of priority. The element with the highest priority is removed first.
Queue Priority Queue

Priority Queue is an extension of Queue with


Queue is a linear data structure.
priority factor embedded.

Follows First In First Out (FIFO)


Serves the element with higher priority first.
algorithm to serve the elements.

Enqueue and dequeue done in O(log n) using


Enqueue and dequeue done in O(1).
binary heaps.

Used in algorithms such as Breadth Used in algorithms such as Dijkstra’s Algorithm,


First Search. Prim’s Algorithms, CPU Scheduling.

*Circular Queue Pseudocode:*


35. What is a circular queue, and how is it different from a regular
queue?
A Circular Queue is an extended version of a normal queue where the last element of
the queue is connected to the first element of the queue forming a circle.
The operations are performed based on FIFO (First In First Out) principle. It is also
called ‘Ring Buffer’.
In a normal Queue, we can insert elements until queue becomes full. But once queue
becomes full, we can not insert the next element even if there is a space in front of queue.
Simple Queue
It is an introductory level queue where insertion occurs at the rear end and deletion at the
front. It is the most common queue type which is experienced by you in daily life, like
people standing at a bus stop. This may be implemented using linear arrays or linked lists in
the computer system. In simple queue time complexity of enqueue(), dequeue(), and peek()
is O(1) and space complexity for each operation is O(1).
Circular Queue
The queue's linear structure always considers the items' forward motion. Every node in the
circular queue connects to the next one, and finally, the last node connects to the first,
forming a circular structure. It is also called a "Ring Buffer" because each end is connected.
Some major applications of a circular queue are; Memory management and CPU planning.
S.No. Simple Queue Circular Queue

1 The data is stored in sequential order, one The last item is connected to the first
after the other. and thus forms a circle.

2 In a simple queue, the elements are The elements can be inserted and
inserted only at the rear position and deleted from any position in a circular
removed from the front. queue.

3 If the rear approaches the end of the array, The rear prevents overflow by wrapping
it could result in an overflow. around the beginning of the array.

4 A simple queue is not much memory A circular queue is more memory


efficient because it can’t reuse the empty efficient because, in array
space produced due to dequeued elements. implementation, it reuses the empty
space produced due to dequeued
elements.

5 It follows the FIFO principle while It follows no specific order for


performing the tasks. execution.

6 It is easier to access the peek value of the The circular queue makes it challenging
simple queue because the front of the to retrieve the peek value because the
queue is always fixed. queue's front and back are not fixed.

7 Effective in applications where overflow Suitable for situations where memory


is not a problem. utilization efficiency is essential.

8 It is used in CPU Scheduling. It is used in Computer Controlled


Traffic Light Systems.

7. Write pseudocode for enqueueing and dequeueing elements in a


circular queue.
The following pseudocode shows the enqueue and dequeue operations of a circular
queue that has been implemented with a zero based array.
Circular Queue Pseudocode:

Initialize an array or list (queue) to store elements.


Initialize two pointers: front and rear.
Set front and rear initially to -1 (indicating an empty queue).

Enqueue(element):
if ((rear + 1) % max_size == front):
return "Queue is full" # Check if the queue is full
else:
if (front == -1):
front = 0
rear = (rear + 1) % max_size
queue[rear] = element

Dequeue():
if (front == -1):
return "Queue is empty" # Check if the queue is empty
else:
removed_element = queue[front]
if (front == rear):
front = rear = -1 # Reset pointers if the queue becomes empty
else:
front = (front + 1) % max_size
return removed_element

In this pseudocode:

max_size represents the maximum capacity of the circular queue.
Enqueue(element) adds an element to the circular queue:
 It checks if the queue is full by comparing (rear + 1) % max_size with
front. If they are equal, the queue is full.
 If the queue is initially empty (front is -1), it sets both front and rear to 0.
 It increments rear (with modulo max_size) and adds the element at the
updated rear position.

Dequeue() removes an element from the circular queue:

 It checks if the queue is empty by examining the value of front (which is
set to -1 when empty).
 If the queue is not empty:
 It retrieves the element at the front position.
 If front becomes equal to rear after dequeueing, it means the queue
becomes empty, so both front and rear are reset to -1.
 Otherwise, it increments front (with modulo max_size) to remove
the element.

OR
enqueue
1 Procedure Enqueue
2 IF iNumberInQueue = 6 THEN
3 OUTPUT "Queue is full"
4 ELSE
5 IF iRear = 6 THEN
6 iRear = 0
7 ELSE
8 iRear = iRear + 1
9 END IF
10 ArrayQueue(iRear) = new data item
11 iNumberInQueue = iNumberInQueue + 1
12END IF
13End Procedure
dequeue
1 Procedure Dequeue
2 IF iNumberInQueue = 0 THEN
3 OUTPUT "Queue is empty"
4 ELSE
5 copy item = ArrayQueue(iFront)
6 iNumberInQueue = iNumberInQueue - 1
7 IF iFront = 6 THEN
8 iFront = 0
9 ELSE
10 iFront = iFront + 1
11 END IF
12END IF
13End Procedure

37. How do you determine if a circular queue is empty?


To determine if a circular queue is empty, you need to check whether both the front
and rear pointers of the circular queue are in their initial state, which typically means
both are set to -1. When both pointers are -1, it indicates that the circular queue
contains no elements. Here's a basic pseudocode function to check if a circular queue
is empty:
Function IsEmpty(circularQueue):
return circularQueue.front == -1 and circularQueue.rear == -1
Explanation:
circularQueue is the circular queue data structure in question.
The function checks if both circularQueue.front and circularQueue.rear are equal to -1.
If they are, it returns true, indicating that the circular queue is empty; otherwise, it
returns false.
This simple check ensures that both pointers are at their initial values, meaning there
are no elements in the circular queue.

*Priority Queue Explain:*


38. What is a priority queue, and when is it used?
A priority queue is an abstract data type that behaves similarly to the normal queue except
that each element has some priority, i.e., the element with the highest priority would come
first in a priority queue. The priority of the elements in a priority queue will determine the
order in which elements are removed from the priority queue.

The priority queue supports only comparable elements, which means that the elements are
either arranged in an ascending or descending order.

For example, suppose we have some values like 1, 3, 4, 8, 14, 22 inserted in a priority queue
with an ordering imposed on the values is from least to the greatest. Therefore, the 1 number
would be having the highest priority while 22 will be having the lowest priority.

A Priority Queue is different from a normal queue, because instead of being a “first-
in-first-out”, values come out in order by priority. It is an abstract data type that
captures the idea of a container whose elements have “priorities” attached to them.
An element of highest priority always appears at the front of the queue. If that
element is removed, the next highest priority element advances to the front. A
priority queue is typically implemented using Heap data structure

Characteristics of a Priority queue

A priority queue is an extension of a queue that contains the following characteristics:

o Every element in a priority queue has some priority associated with it.
o An element with the higher priority will be deleted before the deletion of the
lesser priority.
o If two elements in a priority queue have the same priority, they will be arranged
using the FIFO principle.
Let's understand the priority queue through an example.

We have a priority queue that contains the following values:

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:

o 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.
o 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.
o poll(): It will remove '2' element from the priority queue as it has the highest priority queue.
o 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.

Ascending order priority queue: In ascending order priority queue, a lower priority
number is given as a higher priority in a priority. For example, we take the numbers from 1
to 5 arranged in an ascending order like 1,2,3,4,5; therefore, the smallest number, i.e., 1 is
given as the highest priority in a priority queue.
Descending order priority queue: In descending order priority queue, a higher priority
number is given as a higher priority in a priority. For example, we take the numbers from 1
to 5 arranged in descending order like 5, 4, 3, 2, 1; therefore, the largest number, i.e., 5 is
given as the highest priority in a priority queue.

Representation of priority queue

Now, we will see how to represent the priority queue through a one-way list.
We will create the priority queue by using the list given below in which INFO list contains the data
elements, PRN list contains the priority numbers of each data element available in the INFO list, and LINK
basically contains the address of the next node.

Let's create the priority queue step by step.

In the case of priority queue, lower priority number is considered the higher priority, i.e., lower
priority number = higher priority.

Step 1: In the list, lower priority number is 1, whose data value is 333, so it will be inserted in the list as
shown in the below diagram:

Step 2: After inserting 333, priority number 2 is having a higher priority, and data values associated with
this priority are 222 and 111. So, this data will be inserted based on the FIFO principle; therefore 222 will be
added first and then 111.

Step 3: After inserting the elements of priority 2, the next higher priority number is 4 and data elements
associated with 4 priority numbers are 444, 555, 777. In this case, elements would be inserted based on the
FIFO principle; therefore, 444 will be added first, then 555, and then 777.
Step 4: After inserting the elements of priority 4, the next higher priority number is 5, and the value
associated with priority 5 is 666, so it will be inserted at the end of the queue.

Applications:/Uses of priority queue:


Priority queues are used when you need to manage a set of elements where the order of
processing depends on their priorities. They are particularly useful in situations where
you want to ensure that higher-priority elements are processed before lower-priority
ones. Some common scenarios where priority queues are used include:
The following are the applications of the priority queue:

o It is used in the Dijkstra's shortest path algorithm.


o It is used in prim's algorithm
o It is used in data compression techniques like Huffman code.
o It is used in heap sort.
o It is also used in operating system like priority scheduling, load balancing and interrupt handling.

 Dijkstra’s Shortest Path Algorithm using priority queue: When the graph is
stored in the form of adjacency list or matrix, priority queue can be used to extract
minimum efficiently when implementing Dijkstra’s algorithm.
 Prim’s algorithm: It is used to implement Prim’s Algorithm to store keys of
nodes and extract minimum key node at every step.
 Data compression : It is used in Huffman codes which is used to compresses
data.

How to Implement Priority Queue?


Priority queue can be implemented using the following data structures:
 Arrays
 Linked list
 Heap data structure
 Binary search tree

39. Describe different methods for implementing a priority queue.


1) Implement Priority Queue Using Array:

A simple implementation is to use an array of the following structure.


struct item {
int item;
int priority;
}
 enqueue(): This function is used to insert new data into the queue.
 dequeue(): This function removes the element with the highest priority from
the queue.
 peek()/top(): This function is used to get the highest priority element in the
queue without removing it from the queue.

2) Implement Priority Queue Using Linked List:

In a LinkedList implementation, the entries are sorted in descending order based on


their priority. The highest priority element is always added to the front of the priority
queue, which is formed using linked lists. The functions like push(), pop(),
and peek() are used to implement a priority queue using a linked list and are
explained as follows:
 push(): This function is used to insert new data into the queue.
 pop(): This function removes the element with the highest priority from the
queue.
 peek() / top(): This function is used to get the highest priority element in the
queue without removing it from the queue.

3) Implement Priority Queue Using Heaps:

Binary Heap is generally preferred for priority queue implementation because heaps
provide better performance compared to arrays or LinkedList. Considering the
properties of a heap, The entry with the largest key is on the top and can be removed
immediately. It will, however, take time O(log n) to restore the heap property for the
remaining keys.
Thus the representation of a priority queue as a heap proves advantageous for large n,
since it is represented efficiently in contiguous storage and is guaranteed to require
only logarithmic time for both insertions and deletions.

4) Implement Priority Queue Using Binary Search Tree:

A Self-Balancing Binary Search Tree like AVL Tree, Red-Black Tree, etc. can also
be used to implement a priority queue. Operations like peek(), insert() and delete()
can be performed using BST.
*DQueue Explain:*
41. What is a double-ended queue (deque)?
A double-ended queue, often abbreviated as "deque" (pronounced "deck"), is a linear
data structure that allows you to insert and remove elements from both ends with
constant-time complexity. It's essentially a hybrid between a stack and a queue,
offering the flexibility to perform insertion and deletion operations at both the front
and the rear of the queue.

Though the insertion and deletion in a deque can be performed on both ends, it does
not follow the FIFO rule. The representation of a deque is given as follows -

Types of deque

There are two types of deque -

o Input restricted queue


o Output restricted queue

Input restricted Queue

In input restricted queue, insertion operation can be performed at only one end, while deletion can be
performed from both ends.

Output restricted Queue

In output restricted queue, deletion operation can be performed at only one end, while insertion can be
performed from both ends.
Operations performed on deque

Double-Ended Operations: A deque supports four fundamental operations:

Insertion at the front: You can add elements to the front of the deque.

Removal from the front: You can remove elements from the front.

Insertion at the rear: You can add elements to the rear of the deque.

Removal from the rear: You can remove elements from the rear.

Constant-Time Complexity: The most important feature of a deque is that all of these
operations have a constant-time (O(1)) complexity. This means that the time it takes to
perform these operations doesn't depend on the size of the deque.

Implementation Options: Deques can be implemented using various data structures,


such as arrays or linked lists. The choice of implementation depends on the specific
requirements of your application.

Insertion at the front end


In this operation, the element is inserted from the front end of the queue. Before implementing the operation,
we first have to check whether the queue is full or not. If the queue is not full, then the element can be
inserted from the front end by using the below conditions -

o If the queue is empty, both rear and front are initialized with 0. Now, both will point to the first
element.
o Otherwise, check the position of the front if the front is less than 1 (front < 1), then reinitialize it
by front = n - 1, i.e., the last index of the array.
Insertion at the rear end
In this operation, the element is inserted from the rear end of the queue. Before implementing the operation,
we first have to check again whether the queue is full or not. If the queue is not full, then the element can be
inserted from the rear end by using the below conditions -

o If the queue is empty, both rear and front are initialized with 0. Now, both will point to the first
element.
o Otherwise, increment the rear by 1. If the rear is at last index (or size - 1), then instead of increasing
it by 1, we have to make it equal to 0.

Deletion at the front end


In this operation, the element is deleted from the front end of the queue. Before implementing the operation,
we first have to check whether the queue is empty or not.

If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the deletion. If
the queue is not full, then the element can be inserted from the front end by using the below conditions -

If the deque has only one element, set rear = -1 and front = -1.

Else if front is at end (that means front = size - 1), set front = 0.

Else increment the front by 1, (i.e., front = front + 1).


Deletion at the rear end
In this operation, the element is deleted from the rear end of the queue. Before implementing the operation,
we first have to check whether the queue is empty or not.

If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the deletion.

If the deque has only one element, set rear = -1 and front = -1.

If rear = 0 (rear is at front), then set rear = n - 1.

Else, decrement the rear by 1 (or, rear = rear -1).

42. Explain the advantages of using a deque over a regular queue.


Queue:
The queue is an abstract data type or linear data structure from which elements can
be inserted at the rear(back) of the queue and elements can be deleted from the
front(head) of the queue.

Deque:
The double-ended queue is an abstract data type that generalizes a queue from which
elements can be inserted or deleted either from both front(head) or rear(tail) ends.

Using a deque (double-ended queue) over a regular queue offers several


advantages in situations where you need more versatile data structure capabilities.
Here are the key advantages of using a deque:
Bidirectional Access: Deques allow elements to be inserted and removed from both
ends. In contrast, regular queues (FIFO queues) only support insertion at one end (the
rear) and removal from the other end (the front). This bidirectional access makes
deques more versatile, as you can efficiently perform operations both as a queue (FIFO)
and as a stack (LIFO) with the same data structure. In some scenarios, this can
simplify your code and improve its readability.
Efficient Front Operations: Deques provide efficient front operations like
push_front() and pop_front(), which allow you to insert and remove elements from the
front of the deque in constant time. In contrast, regular queues require additional work
(e.g., using a secondary data structure) to perform efficient front operations.
Dynamic Sizing: Deques can dynamically resize themselves to accommodate more
elements as needed. Regular queues may require additional logic to handle resizing or
might have a fixed capacity.
It's important to note that while deques offer these advantages, they also come with
slightly more overhead in terms of memory usage compared to regular queues.
Difference between Queue and Deque:
S.no Queue Deque

1. A queue is a linear data structure


that stores a collection of A deque (double-ended queue) is a linear
elements, with operations to data structure that stores a collection of
enqueue (add) elements at the elements, with operations to add and
back of the queue, and dequeue remove elements from both ends of the
(remove) elements from the front deque.
of the queue.

2. Elements can only be inserted at Elements can be inserted from both ends
S.no Queue Deque

the end of the data structure. of the data structure.

3. Elements can only be removed


Elements can be removed from both ends
from the front of the data
of the data structure.
structure.

4. Deque can be used to implement the


Queues are a specialized data functionalities of both Stack (LIFO
structure that uses the FIFO approach i.e., Last In First Out) and
approach i.e., First In First Out. Queue (FIFO approach i.e., First In First
Out).

5. A queue can be implemented Deque can be implemented using Circular


using Array or Linked List. Array or Doubly Linked List.

6. Queues can be used as a building Deques can be used as a building block


block for implementing more for implementing more complex data
complex data structures, such as structures, such as double-ended priority
priority queues or stacks. queues or circular buffers.

7. Common deque operations include


addFirst (add an element to the front of
the deque), addLast (add an element to
Common queue operations
the back of the deque), removeFirst
include enqueue, dequeue, peek
(remove the first element from the
(return the front element without
deque), removeLast (remove the last
removing it), and size (return the
element from the deque), peekFirst
number of elements in the queue).
(return the first element without removing
it), and peekLast (return the last element
without removing it).

8. Queue elements cannot be


accessed with the help of an Deque can be traversed using iterator.
iterator.

9. Examples of queue-based Examples of deque-based algorithms


algorithms include Breadth-First include sliding window problems and
Search (BFS) and printing a maintaining a maximum or minimum
binary tree level-by-level. value in a sliding window.
*Linear Probing Numerical Hashing:*
44. What is linear probing in hash tables? Explain Hashing in detail
Hash table is one of the most important data structures that uses a special function known as a hash function
that maps a given value with a key to access the elements faster.

A Hash table is a data structure that stores some information, and the information has basically two main
components, i.e., key and value. The hash table can be implemented with the help of an associative array.
The efficiency of mapping depends upon the efficiency of the hash function used for mapping.

For example, suppose the key value is John and the value is the phone number, so when we pass the key
value in the hash function shown as below:

Hash(key)= index;

Hash(john) = 3;

Drawback of Hash function

A Hash function assigns each value with a unique key. Sometimes hash table uses an imperfect hash
function that causes a collision because the hash function generates the same key of two different values.

Hashing

Hashing is one of the searching techniques that uses a constant time. The time
complexity in hashing is O(1).
In Hashing technique, the hash table and hash function are used. Using the hash function, we can calculate
the address at which the value can be stored.

The main idea behind the hashing is to create the (key/value) pairs. If the key is given, then the algorithm
computes the index at which the value would be stored. It can be written as:

Index = hash(key)

Collision
When the two different values have the same value, then the problem occurs between the two values, known
as a collision. In the above example, the value is stored at index 6. If the key value is 26, then the index
would be:

h(26) = 26%10 = 6

Therefore, two values are stored at the same index, i.e., 6, and this leads to the collision problem. To resolve
these collisions, we have some techniques known as collision techniques.

The following are the collision techniques:

o Open Hashing: It is also known as closed addressing.


o Closed Hashing: It is also known as open addressing.

Open Hashing

In Open Hashing, one of the methods used to resolve the collision is known as a chaining method.

A = 3, 2, 9, 6, 11, 13, 7, 12 where m = 10, and h(k) = 2k+3

In this case, we cannot directly use h(k) = ki/m as h(k) = 2k+3

o The index of key value 3 is:

index = h(3) = (2(3)+3)%10 = 9

The value 3 would be stored at the index 9.

o The index of key value 2 is:

index = h(2) = (2(2)+3)%10 = 7

The value 2 would be stored at the index 7.

o The index of key value 11 is:

index = h(11) = (2(11)+3)%10 = 5

The value 11 would be stored at the index 5. Now, we have two values (6, 11) stored at the same index, i.e.,
5. This leads to the collision problem, so we will use the chaining method to avoid the collision. We will
create one more list and add the value 11 to this list. After the creation of the new list, the newly created list
will be linked to the list having value 6.

Closed Hashing
In Closed hashing, three techniques are used to resolve the collision:

1. Linear probing
2. Quadratic probing
3. Double Hashing technique
Linear Probing

Avoid collision using linear probing


Collision
While hashing, two or more key points to the same hash index under some modulo M
is called as collision.
Linear probing is one of the forms of open addressing. As we know that each cell in the hash table contains a
key-value pair, so when the collision occurs by mapping a new key to the cell already occupied by another
key, then linear probing technique searches for the closest free locations and adds a new key to that empty
cell. In this case, searching is performed sequentially, starting from the position where the collision occurs
till the empty cell is not found.

Linear Probing
Calculate the hash key. key = data % size;
If hashTable[key] is empty, store the value directly. hashTable[key] = data.
If the hash index already has some value, check for next index.
key = (key+1) % size;
If the next index is available hashTable[key], store the value. Otherwise try for next
index.
let’s demonstrate different scenarios of hashing keys and collisions. First, let’s assume
we have the following set of keys and an arbitrary hash function that yields the
following:

Now, suppose our hash table has an arbitrary length of 6, and we want to insert the
remaining elements of :

According to our function , 9 will be inserted at index 5, whereas the last item in
the set that is 2 will be inserted at index 3:
Once we try to insert 2 into our hash table, we encounter a collision with
key 310. However, because we’re using linear probing as our collision resolution
algorithm, our hash table results in the following state after inserting all elements in :

Now, let’s dive into the linear probing algorithm and learn how we obtained the
previous state in our hash table.

SECOND EXAMPLE:(agr vo smjhna aaye to)…

Linear Probing Procedure


Initial Hash Table

Insert 13
insert 1

Insert 6

1 % 5 = 1.
6 % 5 = 1.
Both 1 and 6 points the same index under modulo 5.
So that we have placed 6 in arr[2] which is next available index.

Insert 11
1 % 5 = 1.
6 % 5 = 1.
11 % 5 = 1.
Both 1, 6 and 11 points the same index under modulo 5.
So that we have placed 11 in arr[4] which is next available index.

Insert 10

Insert 15
15 % 5 = 0.
Hash table don't have any empty index. So, we can't insert the data.
8. Explain the collision resolution process in linear probing.
Upr vala hi dekh lenaa
Avoid collision using linear probing
Collision
While hashing, two or more key points to the same hash index under some modulo M
is called as collision.
Linear probing is one of the forms of open addressing. As we know that each cell in the hash table contains a
key-value pair, so when the collision occurs by mapping a new key to the cell already occupied by another
key, then linear probing technique searches for the closest free locations and adds a new key to that empty
cell. In this case, searching is performed sequentially, starting from the position where the collision occurs
till the empty cell is not found.

Linear Probing
Calculate the hash key. key = data % size;
If hashTable[key] is empty, store the value directly. hashTable[key] = data.
If the hash index already has some value, check for next index.
key = (key+1) % size;
If the next index is available hashTable[key], store the value. Otherwise try for next
index.
let’s demonstrate different scenarios of hashing keys and collisions. First, let’s assume
we have the following set of keys and an arbitrary hash function that yields the
following:
Now, suppose our hash table has an arbitrary length of 6, and we want to insert the
remaining elements of :

According to our function , 9 will be inserted at index 5, whereas the last item in
the set that is 2 will be inserted at index 3:

Once we try to insert 2 into our hash table, we encounter a collision with
key 310. However, because we’re using linear probing as our collision resolution
algorithm, our hash table results in the following state after inserting all elements in :

Now, let’s dive into the linear probing algorithm and learn how we obtained the
previous state in our hash table.

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