0% found this document useful (0 votes)
11 views10 pages

TD Queue Correction

The document provides an overview of queues, a linear data structure that operates on a FIFO basis, detailing its operations, types, and applications. It discusses various queue implementations, including simple, circular, and priority queues, along with practical exercises for implementing these structures using arrays and stacks. Additionally, it covers concepts like queue overflow and underflow, and specific use cases such as CPU scheduling and finding the first non-repeating character in a stream.

Uploaded by

NESSRIN HANA
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)
11 views10 pages

TD Queue Correction

The document provides an overview of queues, a linear data structure that operates on a FIFO basis, detailing its operations, types, and applications. It discusses various queue implementations, including simple, circular, and priority queues, along with practical exercises for implementing these structures using arrays and stacks. Additionally, it covers concepts like queue overflow and underflow, and specific use cases such as CPU scheduling and finding the first non-repeating character in a stream.

Uploaded by

NESSRIN HANA
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/ 10

TD Queue

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:

Consider a queue of people in line at a ticket counter:

 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

A queue supports the following basic operations:

 enqueue: Adds an element to the rear of the queue.


 dequeue: Removes the element from the front of the queue.
 peek: Returns the element at the front without removing it.
 is_empty: Checks whether the queue is empty.
 is_full: Checks whether the queue is full (if there’s a fixed size).

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:

 enqueue: Adds an element to the rear of the queue.


 dequeue: Removes an element from the front of the queue, shifting all remaining
elements forward in a linear queue (or using a circular buffer in a circular queue).

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:

 Simple Queue: Used in scenarios like handling print jobs in a printer.


 Circular Queue: Ideal for scenarios where the queue needs to handle elements in a
continuous cycle, such as round-robin scheduling in CPU scheduling.
 Priority Queue: Used in task scheduling where tasks with higher priority are executed
before lower priority ones, such as in operating system schedulers.

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.

5. Circular Queue vs. Linear Queue

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.

6. Queue Overflow and Underflow

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

7. Queue as a Buffer in Networking

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.

9. Double-Ended Queue (Deque)


Page 3 of 10
A deque (double-ended queue) allows insertion and removal of elements from both ends of the
queue. It can function as both a stack and a queue, providing more flexibility in managing data.

Operations:

 Add First: Insert an element at the front.


 Add Last: Insert an element at the rear.
 Remove First: Remove an element from the front.
 Remove Last: Remove an element from the rear.

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

Exercise 1: Implement a Queue Using Arrays

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.

// Initialize the queue


void initializeQueue(Queue *q) {
q->rear = -1; // Initialize rear to -1 (indicating an empty queue)
}

// Check if the queue is empty


int is_empty(Queue *q) {
return q->rear == -1; // Queue is empty if rear is -1
}

// Check if the queue is full


int is_full(Queue *q) {
return q->rear == MAX_SIZE - 1; // Queue is full if rear reaches MAX_SIZE - 1
}

// Enqueue an element into the queue


void enqueue(Queue *q, int value) {
if (is_full(q)) {
printf("Queue is full! Cannot enqueue %d\n", value);
return;
}
q->queue[++(q->rear)] = value; // Insert the element and increment rear
printf("Enqueued %d\n", value);
}

// Dequeue an element from the queue


int dequeue(Queue *q) {
if (is_empty(q)) {
printf("Queue is empty! Cannot dequeue.\n");
return -1; // Return -1 to indicate empty queue
}
int value = q->queue[0]; // Get the value to be dequeued
// Shift elements to the left to fill the empty spot
for (int i = 0; i < q->rear; i++) {
q->queue[i] = q->queue[i + 1];
}
q->rear--; // Decrease the rear index
printf("Dequeued %d\n", value);
return value;
}

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
}

// Circular Queue Implementation

// Enqueue operation for a circular queue


void enqueue_circular(Queue *q, int value) {
if ((q->rear + 1) % MAX_SIZE == 0) {
printf("Queue is full! Cannot enqueue %d\n", value);
return;
}
q->queue[(q->rear + 1) % MAX_SIZE] = value;
q->rear = (q->rear + 1) % MAX_SIZE;
printf("Enqueued %d to circular queue\n", value);
}

Exercise 2: Implement a Circular Queue

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.

Functions in queue.h (for Circular Queue):

1. enqueue_circular(Queue *q, int value):


o Goal: Adds an element to the queue in a circular manner. It uses the modulo
operation to determine the appropriate index for insertion. If the queue is full, it
prints an error message. Otherwise, it inserts the value and updates the rear index
circularly.
2. dequeue_circular(Queue *q):
o Goal: Removes and returns the front element of the circular queue. The front
pointer is updated in a circular manner by incrementing the index and wrapping
around if necessary. If the queue is empty, it prints an error message.

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.

Summary of Functions’ Goals in queue.h:

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

// Dequeue operation for a circular queue


int dequeue_circular(Queue *q) {
if (is_empty(q)) {
printf("Queue is empty! Cannot dequeue.\n");
return -1; // Return -1 to indicate empty queue
}
int value = q->queue[0];
// Shift elements to the left circularly
for (int i = 0; i < q->rear; i++) {
q->queue[i] = q->queue[i + 1];
}
q->rear = (q->rear - 1 + MAX_SIZE) % MAX_SIZE; // Circularly update the rear
printf("Dequeued %d from circular queue\n", value);
return value;
}

3. Queue Reversal Using Two Stacks

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);
}
}

4. Simulate a Queue Using Two Stacks

To implement a queue using two stacks (stack1 for enqueue and stack2 for dequeue), we will
use the following strategy:

1. For enqueue operation: Push elements onto stack1.


2. For dequeue operation: If stack2 is empty, pop all elements from stack1 and push
them onto stack2, then pop from stack2.

Steps:

1. Enqueue: Push to stack1.


2. Dequeue: If stack2 is empty, transfer all elements from stack1 to stack2, then pop
from stack2.

void enqueueUsingTwoStacks(Queue *q, Stack *stack1, Stack *stack2, int value) {


push(stack1, value);
}

// Function to dequeue an element from the simulated queue


int dequeueUsingTwoStacks(Queue *q, Stack *stack1, Stack *stack2) {
if (is_empty_s(stack2)) {
// Transfer all elements from stack1 to stack2
while (!is_empty_s(stack1)) {
int value = pop(stack1);

Page 8 of 10
push(stack2, value);
}
}

// If stack2 is still empty, the queue is empty


if (is_empty_s(stack2)) {
printf("Queue is empty.\n");
return -1;
}

// Pop from stack2 to simulate dequeue


return pop(stack2);
}

5. First Non-Repeating Character in a Stream

To find the first non-repeating character in a stream using a queue, we will:

1. Use a queue to maintain the order of characters.


2. Use a hashmap (or an array in this case) to keep track of the frequency of characters.
3. After each character is inserted, we will dequeue characters from the front of the queue as
long as they have a frequency greater than 1.

Steps:

1. Add characters to the queue and maintain the frequency.


2. After each insertion, check the front of the queue to find the first non-repeating character.

// Function to print the first non-repeating character in the stream


void firstNonRepeatingCharacter(const char *stream) {
Queue q;
initializeQueue(&q);
int freq[256] = {0}; // Frequency of each character

for (int i = 0; stream[i] != '\0'; i++) {


char currentChar = stream[i];

// Enqueue the current character and update its frequency


enqueue(&q, currentChar);
freq[currentChar]++;

// 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);
}

// Print the first non-repeating character


if (!is_empty(&q)) {
printf("First non-repeating character: %c\n", q.queue[0]);
} else {
printf("No non-repeating character\n");
}
}
}

Explanation of the Key Functions:

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

You might also like