TD Queue Correction
TD Queue Correction
TheoreƟcal Problems
1. What is a Queue?
A queue is a linear data structure that follows the FIFO (First In, First Out) principle, meaning
the first element added to the queue will be the first one to be removed.
Example:
The person who arrives first gets their ticket first, and the person who arrives last waits
until everyone before them has been served.
A stack, on the other hand, follows the LIFO (Last In, First Out) principle, meaning the last
element added is the first one to be removed. The key difference between a queue and a stack is
the order in which elements are removed: a stack removes elements from the top, whereas a
queue removes elements from the front.
2. Queue Operations
Time complexities:
enqueue and dequeue: Both operations take O(1) time for a simple queue, meaning they
occur in constant time.
peek: Takes O(1) time, as it only accesses the front element.
is_empty and is_full: Both take O(1) time, as they only check the state of the queue.
Implementation:
Page 1 of 10
3. Types of Queues
Simple Queue: The basic type of queue where elements are added at the rear and
removed from the front. It is also called a linear queue.
Circular Queue: This is an extension of a simple queue where, once the rear pointer
reaches the end of the queue, it wraps around to the beginning of the queue, thus utilizing
all available space in a fixed-size array. It resolves the issue of wasted space in a simple
queue.
Priority Queue: A queue where each element has a priority associated with it. Elements
with higher priority are dequeued before those with lower priority, regardless of their
insertion order. This can be implemented using a heap or a sorted list.
Use Cases:
4. Applications of Queues
1. CPU Scheduling: Queues are used to manage processes in an operating system, where
processes are executed in the order they arrive (FIFO) or based on priority.
2. Task Scheduling: In real-time systems, queues manage tasks in the order they need to be
processed (FIFO or priority-based).
3. Networking: Queues are used in packet switching, where packets are queued for
transmission and processed in order of arrival.
A circular queue is a queue that connects the last position of the array back to the first position,
allowing efficient use of the array by preventing unused space.
In a linear queue, once the rear pointer reaches the end of the array, new elements
cannot be added unless the queue is resized or reset.
In a circular queue, the rear pointer wraps around, making it more efficient for
scenarios with continuous input, like buffering in networking.
Page 2 of 10
Circular Queue Advantage: It prevents wasted space in a fixed-size array, whereas a linear
queue might waste space when the front elements are removed but new elements cannot be
inserted in the same position.
Queue Overflow: Occurs when the queue is full and an attempt to enqueue another
element is made. This can be prevented by checking if the queue is full before inserting a
new element.
Queue Underflow: Occurs when trying to dequeue an element from an empty queue.
This can be prevented by checking if the queue is empty before performing a dequeue
operation.
In networking, queues are used as buffers to temporarily store packets in routers or network
devices. This helps manage bursts of network traffic, ensuring that data packets are transmitted
in the correct order and without loss.
Why a Queue?:
A queue ensures packets are processed in the order they are received (FIFO), preventing
data loss due to congestion or packet collision.
8. Priority Queue
A priority queue is a special type of queue where each element is associated with a priority
value. Elements with higher priority are dequeued before those with lower priority, even if they
were added later.
Difference from Normal Queue: In a normal queue, elements are processed in the order
they are added (FIFO), but in a priority queue, elements are processed based on their
priority.
Example Application: Task scheduling in operating systems where processes with higher
priority are executed before lower-priority ones.
Operations:
Example Use Case: A deque can be used for sliding window problems where elements need to
be added or removed from both ends of a sequence, such as finding the maximum in a sliding
window of numbers.
PracƟcal Problems
Goal:
The goal of this exercise is to implement a basic queue data structure using an array. A queue
follows the FIFO (First In, First Out) principle, meaning the first element enqueued is the first
element to be dequeued.
Functions in queue.h:
1. initializeQueue(Queue *q):
o Goal: Initializes the queue. The rear is set to -1, indicating that the queue is
empty. This prepares the queue for further operations.
2. is_empty(Queue *q):
o Goal: Checks if the queue is empty by verifying if the rear index is -1. Returns 1
if the queue is empty, otherwise returns 0.
3. is_full(Queue *q):
o Goal: Checks if the queue is full by verifying if the rear index has reached the
maximum size of the queue (MAX_SIZE - 1). Returns 1 if the queue is full,
otherwise returns 0.
4. enqueue(Queue *q, int value):
o Goal: Adds an element to the queue at the rear position. If the queue is full, it
prints an error message. If the queue is not full, the value is added and the rear
index is incremented.
5. dequeue(Queue *q):
o Goal: Removes and returns the front element of the queue (the element at index
0). All other elements are shifted left, and the rear index is updated accordingly.
If the queue is empty, it prints an error message.
Page 4 of 10
6. peek(Queue *q):
o Goal: Returns the front element of the queue without removing it. If the queue is
empty, it prints an error message.
Page 5 of 10
// Peek at the front element of the queue
int peek(Queue *q) {
if (is_empty(q)) {
printf("Queue is empty! No front element.\n");
return -1; // Return -1 to indicate empty queue
}
return q->queue[0]; // Return the front element
}
Goal:
The goal of this exercise is to implement a circular queue, which solves the issue of a regular
queue becoming full when elements are removed, but space is still available. In a circular queue,
the rear and front pointers can wrap around, utilizing unused space in the array.
Page 6 of 10
Key Differences Between Regular Queue and Circular Queue:
Regular Queue: In a regular queue, the rear index increments until it reaches the
maximum size. If space is freed by dequeueing, no further elements can be enqueued
unless the rear index is reset or elements are shifted.
Circular Queue: In a circular queue, the rear index wraps around when it reaches the
end of the array, allowing better space utilization by filling in the gaps left by dequeued
elements.
enqueue_circular: Adds an element to the queue in a circular manner, utilizing the full
array size by wrapping around when needed.
dequeue_circular: Removes an element in a circular fashion, updating the front
pointer appropriately.
To reverse a queue using two stacks, we'll first dequeue all the elements from the queue and push
them onto the first stack. After that, we will pop from the first stack and enqueue the elements
back into the queue, effectively reversing their order.
Steps:
1. Dequeue elements from the queue and push them onto stack1.
2. Pop elements from stack1 and enqueue them back into the queue.
Page 7 of 10
// Function to reverse the queue using two stacks
void reverseQueue(Queue *q) {
Stack stack1;
initStack(&stack1); // Initialize stack1
// Step 1: Dequeue elements from the queue and push them to stack1
while (!is_empty(q)) {
int value = dequeue(q);
push(&stack1, value);
}
// Step 2: Pop elements from stack1 and enqueue them back to the queue
while (!is_empty(&stack1)) {
int value = pop(&stack1);
enqueue(q, value);
}
}
To implement a queue using two stacks (stack1 for enqueue and stack2 for dequeue), we will
use the following strategy:
Steps:
Page 8 of 10
push(stack2, value);
}
}
Steps:
// Dequeue characters from the front of the queue if they are repeating
Page 9 of 10
while (!is_empty(&q) && freq[q.queue[0]] > 1) {
dequeue(&q);
}
1. Queue Operations:
o initializeQueue(), is_empty(), is_full(), enqueue(), and dequeue() are
used to manage the elements inside the queue.
2. Stack Operations:
o initStack(), is_empty(), push(), and pop() are used to manage stack
operations in exercises 3 and 4.
Page 10 of 10