Queues are fundamental linear data structures that operate on the First-In-First-Out (FIFO) principle, making them ideal for tasks like scheduling, buffering, and managing resources. On Day 9, I explored different queue implementations and solved problems that highlight their practical applications.
Check out my GitHub repository for all the solutions and progress updates at: 100 Days of DSA
Excited to dive into today’s challenges and continue the journey of learning and problem-solving!
1. Implement a Queue Using Arrays
This program allows the user to perform operations such as enqueue (add an element), dequeue (remove an element), peek (view the front element), and display (show all elements in the queue). The queue supports circular behavior using modular arithmetic to efficiently utilize array space. Overflow and underflow conditions are handled to prevent errors.
Code:
#include <iostream>
using namespace std;
class Queue {
private:
int front, rear, size;
int *arr;
public:
// Constructor to initialize the queue
Queue(int capacity) {
size = capacity;
front = -1;
rear = -1;
arr = new int[size];
}
// Destructor to deallocate memory
~Queue() {
delete[] arr;
}
// Check if the queue is full
bool isFull() {
return (rear + 1) % size == front;
}
// Check if the queue is empty
bool isEmpty() {
return front == -1;
}
// Add an element to the queue
void enqueue(int x) {
if (isFull()) {
cout << "Queue Overflow! Cannot insert " << x << endl;
return;
}
if (isEmpty()) {
front = 0; // Initialize front if the queue is empty
}
rear = (rear + 1) % size;
arr[rear] = x;
cout << x << " enqueued to the queue." << endl;
}
// Remove an element from the queue
int dequeue() {
if (isEmpty()) {
cout << "Queue Underflow! Cannot dequeue." << endl;
return -1;
}
int data = arr[front];
if (front == rear) {
// Reset the queue if it becomes empty after dequeue
front = -1;
rear = -1;
}
else
front = (front + 1) % size; // Update front in a circular manner
return data;
}
// Get the front element of the queue
int peek() {
if (isEmpty()) {
cout << "Queue is Empty!" << endl;
return -1;
}
return arr[front];
}
// Display the queue elements
void display() {
if (isEmpty()) {
cout << "Queue is Empty!" << endl;
return;
}
cout << "Queue elements are: ";
int i = front;
while (true) {
cout << arr[i] << " ";
if (i == rear)
break;
i = (i + 1) % size;
}
cout << endl;
}
};
int main() {
int n, choice, value;
cout << "Enter the size of the queue: ";
cin >> n;
Queue q(n);
do {
cout << "\nQueue Operations Menu:\n"
<< "1. Enqueue\n"
<< "2. Dequeue\n"
<< "3. Peek\n"
<< "4. Display\n"
<< "5. Exit\n"
<< "Enter your choice: ";
cin >> choice;
switch (choice) {
case 1:
cout << "Enter the value to enqueue: ";
cin >> value;
q.enqueue(value);
break;
case 2:
value = q.dequeue();
if (value != -1)
cout << "Dequeued value: " << value << endl;
break;
case 3:
value = q.peek();
if (value != -1)
cout << "Front of the queue: " << value << endl;
break;
case 4:
q.display();
break;
case 5:
cout << "Exiting program." << endl;
break;
default:
cout << "Invalid choice! Please try again." << endl;
}
} while (choice != 5);
return 0;
}
Output:
2. Implement a Circular Queue
This program implements a circular queue using an array. It handles basic queue operations like enqueue, dequeue, and peek while efficiently managing the circular behavior of the queue using modular arithmetic. When an element is added, it updates the rear
pointer, and when an element is removed, it updates the front
pointer. The queue automatically wraps around when the rear
or front
reaches the end of the array, allowing it to reuse empty spaces. It also includes checks for when the queue is full or empty, ensuring that operations are valid.
Code:
#include <iostream>
using namespace std;
class CircularQueue {
private:
int front, rear, size;
int *queue;
public:
CircularQueue(int n) {
size = n;
queue = new int[size];
front = -1;
rear = -1;
}
~CircularQueue() {
delete[] queue;
}
bool isFull() {
return (front == (rear + 1) % size);
}
bool isEmpty() {
return (front == -1);
}
void enqueue(int value) {
if (isFull()) {
cout << "Queue is Full! Cannot enqueue " << value << "." << endl;
return;
}
if (isEmpty()) {
front = 0;
}
rear = (rear + 1) % size;
queue[rear] = value;
cout << value << " enqueued to the queue." << endl;
}
void dequeue() {
if (isEmpty()) {
cout << "Queue is Empty! Cannot dequeue." << endl;
return;
}
cout << "Dequeued " << queue[front] << " from the queue." << endl;
if (front == rear) {
// Queue becomes empty
front = rear = -1;
}
else
front = (front + 1) % size;
}
void peek() {
if (isEmpty())
cout << "Queue is Empty! Nothing at the front." << endl;
else
cout << "Front element is: " << queue[front] << endl;
}
void display() {
if (isEmpty()) {
cout << "Queue is Empty!" << endl;
return;
}
cout << "Queue elements are: ";
int i = front;
while (true) {
cout << queue[i] << " ";
if (i == rear)
break;
i = (i + 1) % size;
}
cout << endl;
}
};
int main() {
int n, choice, value;
cout << "Enter the size of the Circular Queue: ";
cin >> n;
CircularQueue cq(n);
while (true) {
cout << "\nMenu:\n";
cout << "1. Enqueue\n";
cout << "2. Dequeue\n";
cout << "3. Peek\n";
cout << "4. Display\n";
cout << "5. Exit\n";
cout << "Enter your choice: ";
cin >> choice;
switch (choice) {
case 1:
cout << "Enter the value to enqueue: ";
cin >> value;
cq.enqueue(value);
break;
case 2:
cq.dequeue();
break;
case 3:
cq.peek();
break;
case 4:
cq.display();
break;
case 5:
cout << "Exiting the program." << endl;
return 0;
default:
cout << "Invalid choice! Please try again." << endl;
}
}
}
Output:
3. Implement a Deque Using Arrays
This program implements a circular deque using an array. It allows the user to insert and remove elements from both the front and rear of the deque. The operations are handled through a menu-driven interface, where users can perform actions like inserting at the front or rear, deleting from the front or rear, and retrieving the front or rear element. The deque is implemented using an array with circular indexing to efficiently manage space.
Code:
#include <iostream>
using namespace std;
#define MAX 1000
class Deque {
int arr[MAX];
int front, rear;
public:
Deque() {
front = -1;
rear = -1;
}
// Check if the deque is empty
bool isEmpty() {
return (front == -1);
}
// Check if the deque is full
bool isFull() {
return (front == 0 && rear == MAX - 1) || (front == rear + 1);
}
// Insert an element at the front
void insertFront(int x) {
if (isFull()) {
cout << "Deque is full!" << endl;
return;
}
if (front == -1)
front = rear = 0;
else if (front == 0)
front = MAX - 1;
else
front--;
arr[front] = x;
cout << x << " inserted at the front" << endl;
}
// Insert an element at the rear
void insertRear(int x) {
if (isFull()) {
cout << "Deque is full!" << endl;
return;
}
if (front == -1)
front = rear = 0;
else if (rear == MAX - 1)
rear = 0;
else
rear++;
arr[rear] = x;
cout << x << " inserted at the rear" << endl;
}
// Delete an element from the front
void deleteFront() {
if (isEmpty()) {
cout << "Deque is empty!" << endl;
return;
}
cout << arr[front] << " deleted from the front" << endl;
if (front == rear)
front = rear = -1;
else if (front == MAX - 1)
front = 0;
else
front++;
}
// Delete an element from the rear
void deleteRear() {
if (isEmpty()) {
cout << "Deque is empty!" << endl;
return;
}
cout << arr[rear] << " deleted from the rear" << endl;
if (front == rear)
front = rear = -1;
else if (rear == 0)
rear = MAX - 1;
else
rear--;
}
// Get the front element
int getFront() {
if (isEmpty()) {
cout << "Deque is empty!" << endl;
return -1;
}
return arr[front];
}
// Get the rear element
int getRear() {
if (isEmpty()) {
cout << "Deque is empty!" << endl;
return -1;
}
return arr[rear];
}
};
int main() {
Deque dq;
int choice, value;
while (true) {
cout << "\nDeque Operations Menu: ";
cout << "\n1. Insert at Front";
cout << "\n2. Insert at Rear";
cout << "\n3. Delete from Front";
cout << "\n4. Delete from Rear";
cout << "\n5. Get Front";
cout << "\n6. Get Rear";
cout << "\n7. Exit";
cout << "\nEnter your choice: ";
cin >> choice;
switch (choice) {
case 1:
cout << "Enter value to insert at front: ";
cin >> value;
dq.insertFront(value);
break;
case 2:
cout << "Enter value to insert at rear: ";
cin >> value;
dq.insertRear(value);
break;
case 3:
dq.deleteFront();
break;
case 4:
dq.deleteRear();
break;
case 5:
cout << "Front element: " << dq.getFront() << endl;
break;
case 6:
cout << "Rear element: " << dq.getRear() << endl;
break;
case 7:
cout << "Exiting..." << endl;
return 0;
default:
cout << "Invalid choice!" << endl;
}
}
return 0;
}
Output:
4. Reverse First K Elements of a Queue
The code reverses the first k
elements of a queue using a stack. It first dequeues the first k
elements and pushes them onto a stack, which reverses their order due to the LIFO (Last-In-First-Out) nature of the stack. Then, it pops the elements from the stack and enqueues them back to the queue, reversing their order. Finally, it moves the remaining elements from the front of the queue to the back, preserving their original order.
Code:
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
void reverse_first_k_elements(queue<int> &q, int k) {
// If k is greater than the size of the queue, we do nothing.
if (k <= 0 || k > q.size())
return;
stack<int> s;
// Step 1: Dequeue the first k elements and push them into a stack
for (int i = 0; i < k; i++) {
s.push(q.front());
q.pop();
}
// Step 2: Enqueue the elements from the stack to the queue (reversed order)
while (!s.empty()) {
q.push(s.top());
s.pop();
}
// Step 3: Dequeue the remaining elements and enqueue them back to the queue
int remainingElements = q.size() - k;
for (int i = 0; i < remainingElements; i++) {
q.push(q.front());
q.pop();
}
}
int main() {
queue<int> q;
int n, k;
cout << "Enter the size of the queue: ";
cin >> n;
cout << "Enter the elements of the queue: ";
for (int i = 0; i < n; i++) {
int val;
cin >> val;
q.push(val);
}
cout << "Enter the value of k (number of elements to reverse): ";
cin >> k;
reverse_first_k_elements(q, k);
cout << "Queue after reversing the first " << k << " elements: ";
while (!q.empty()) {
cout << q.front() << " ";
q.pop();
}
return 0;
}
Output:
5. Sliding Window Maximum Using a Deque
This program uses a deque to efficiently calculate the maximum element in each sliding window of size k
. The deque stores indices of the elements in the current window, ensuring that the largest element's index is always at the front. As the window slides, elements are added and removed from the deque, and the maximum value for each window is printed.
Code:
#include <iostream>
#include <deque>
using namespace std;
void sliding_window_maximum(int arr[], int n, int k) {
// Create a deque to store indices of elements in the window
deque<int> dq;
// Loop through the array
for (int i = 0; i < n; i++) {
// Remove elements from the front of the deque if they're out of the current window
if (!dq.empty() && dq.front() <= i - k)
dq.pop_front();
// Remove elements from the back of the deque if they are smaller than the current element because they are not useful for future windows
while (!dq.empty() && arr[dq.back()] <= arr[i])
dq.pop_back();
// Add the current element's index to the deque
dq.push_back(i);
// The first element in the deque is the maximum element in the current window
if (i >= k - 1)
cout << arr[dq.front()] << " ";
}
cout << endl;
}
int main() {
int n, k;
cout << "Enter the size of the array: ";
cin >> n;
int arr[n];
cout << "Enter the elements of the array: ";
for (int i = 0; i < n; i++)
cin >> arr[i];
cout << "Enter the size of the sliding window: ";
cin >> k;
// Checking if the window size is valid
if (k <= 0 || k > n) {
cout << "Invalid window size!" << endl;
return 0;
}
cout << "Sliding window maximums: ";
sliding_window_maximum(arr, n, k);
return 0;
}
Output:
That wraps up Day 9 of my 100 Days of DSA challenge! Today, I explored the powerful world of queues and their variations. By diving into problems like circular queues and deques, I gained a deeper understanding of how these structures can streamline solutions for complex problems.
I’m looking forward to the new challenges tomorrow, where I’ll continue to enhance my problem-solving skills and dive deeper into data structures. Stay tuned for more! 🚀