DSA1 Questions
DSA1 Questions
==>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
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:-
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.
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
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
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.
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;
return 0;
}
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.
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:
Initialize an empty stack (e.g., an array or linked list) and a top pointer (an integer).
Push(element):
if stack is full:
else:
stack[top] = element
Pop():
if stack is empty:
else:
element = stack[top]
return element
Top():
if stack is empty:
else:
return stack[top]
IsEmpty():
IsFull():
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).
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.
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.
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.
Infix Expression: (A + B) * C - D / E
Operator '+': Since the stack is empty, push '+' onto the stack.
Stack: +
Output Queue: A
Character ')': Pop operators from the stack and add them to the output queue until
we encounter the opening parenthesis '('.
Stack: (empty)
Stack: *
Stack: *
Operator '-': Pop operators from the stack (which only contains '*') and add them to
the output queue. Then, push '-' onto the stack.
Stack: -
Stack: -
Stack: /
Stack: /
After scanning the entire expression, pop any remaining operators from the stack
and add them to the output queue.
Stack: (empty)
.Initialize an empty stack for operators and an empty list for the output.
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.
Input: A * (B + C)
Output (Postfix): A B C + *
Scan the infix expression from left to right, one character at a time.
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.
a a push a in stack
b a, b push b in stack
c a, b, c push c in stack
d a + b – c, d push d in stack
a + b – c, d ,
e push e in stack
e
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.
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.
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.
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 .
2. Only the uppermost disk from one stack can be moved on to the top of another
stack or an empty rod.
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 !!
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:
if n == 1:
moveDisk(source, target)
else:
// Move n-1 disks from source to auxiliary using target as the auxiliary peg
// Move the n-1 disks from auxiliary to target using source as the auxiliary peg
In this pseudocode:
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).
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>
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
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
int main() {
towerOfHanoi(n, 'A', 'B', 'C'); // Calling the function with three pegs: A, B, and C
return 0;
}
In this C++ code:
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.
return dequeued_element
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.
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.
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:
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:
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.
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
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.
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.
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
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
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.
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.
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.
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.
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.
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.
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
In input restricted queue, insertion operation can be performed at only one end, while deletion can be
performed from both ends.
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
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.
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.
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.
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.
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.
2. Elements can only be inserted at Elements can be inserted from both ends
S.no Queue Deque
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;
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.
Open Hashing
In Open Hashing, one of the methods used to resolve the collision is known as a chaining method.
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
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.
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.