🛠️ Settings
Understanding Queues in Data Structures
A queue
is a linear data structure that follows the First In, First Out
(FIFO) principle. This means that the element inserted first will be the one to be removed first, just like a real-world queue. Queues are commonly used in scenarios where we need to manage objects in an order, such as task scheduling, handling requests in a server, or implementing breadth-first search (BFS) in graph algorithms.
Queues are widely used in scenarios like scheduling processes in operating system, handling requests in web servers, and in breadth-first search algorithms.
Basic Operations on a Queue
A queue typically supports the following operations:
- Enqueue: Add an element to the end of the queue.
- Dequeue: Remove the element from the front of the queue.
- Front/Peek: Get the front element of the queue.
- Rear: Get the last element of the queue.
- isEmpty: Check if the queue is empty.
- isFull: Check if the queue is full (for a bounded queue).
Types of Queues
- Simple Queue: Also known as a linear queue, where insertion is done at the rear and deletion is done at the front.
- Circular Queue: A more efficient queue where the last position is connected back to the first position to make a circle. This helps in utilizing the space once elements are dequeued.
- Priority Queue: Each element is assigned a priority, and elements are dequeued based on their priority rather than their position in the queue.
- Deque (Double-Ended Queue): A queue where insertion and deletion can be performed at both the front and rear ends.
1️⃣ Array Implementation of Queue:
- Fixed Size
#include <iostream>
using namespace std;
#define SIZE 5
class Queue {
private:
int items[SIZE];
int front, rear;
public:
Queue() : front(-1), rear(-1) {}
bool isFull() {
return rear == SIZE - 1;
}
bool isEmpty() {
return front == -1;
}
void enqueue(int element) {
if (isFull()) {
cout << "Queue is full!" << endl;
return;
}
if (isEmpty()) {
front = 0;
}
items[++rear] = element;
cout << "Inserted " << element << endl;
}
int dequeue() {
if (isEmpty()) {
cout << "Queue is empty!" << endl;
return -1;
}
int element = items[front];
if (front >= rear) { // Queue has only one element
front = rear = -1;
} else {
front++;
}
cout << "Deleted " << element << endl;
return element;
}
void display() {
if (isEmpty()) {
cout << "Queue is empty!" << endl;
return;
}
cout << "Queue elements are: ";
for (int i = front; i <= rear; i++) {
cout << items[i] << " ";
}
cout << endl;
}
};
int main() {
Queue q;
q.enqueue(1);
q.enqueue(2);
q.enqueue(3);
q.enqueue(4);
q.enqueue(5);
q.enqueue(6); // Queue is full
q.display();
q.dequeue();
q.display();
return 0;
}
Another Way:
#include <bits/stdc++.h>
using namespace std;
// Class implementing Queue using Arrays
class ArrayQueue {
// Array to store queue elements
int* arr;
// Indices for start and end of the queue
int start, end;
// Current size and maximum size of the queue
int currSize, maxSize;
public:
// Constructor
ArrayQueue() {
arr = new int[10];
start = -1;
end = -1;
currSize = 0;
maxSize = 10;
}
// Method to push an element into the queue
void push(int x) {
// Check if the queue is full
if (currSize == maxSize) {
cout << "Queue is full\nExiting..." << endl;
exit(1);
}
// If the queue is empty, initialize start and end
if (end == -1) {
start = 0;
end = 0;
}
else {
// Circular increment of end
end = (end + 1) % maxSize;
}
arr[end] = x;
currSize++;
}
// Method to pop an element from the queue
int pop() {
// Check if the queue is empty
if (start == -1) {
cout << "Queue Empty\nExiting..." << endl;
exit(1);
}
int popped = arr[start];
// If the queue has only one element, reset start and end
if (currSize == 1) {
start = -1;
end = -1;
}
else {
// Circular increment of start
start = (start + 1) % maxSize;
}
currSize--;
return popped;
}
// Method to get the front element of the queue
int peek() {
// Check if the queue is empty
if (start == -1) {
cout << "Queue is Empty" << endl;
exit(1);
}
return arr[start];
}
// Method to determine whether the queue is empty
bool isEmpty() {
return (currSize == 0);
}
};
int main() {
ArrayQueue queue;
vector<string> commands = {"ArrayQueue", "push", "push",
"peek", "pop", "isEmpty"};
vector<vector<int>> inputs = {{}, {5}, {10}, {}, {}, {}};
for (int i = 0; i < commands.size(); ++i) {
if (commands[i] == "push") {
queue.push(inputs[i][0]);
cout << "null ";
} else if (commands[i] == "pop") {
cout << queue.pop() << " ";
} else if (commands[i] == "peek") {
cout << queue.peek() << " ";
} else if (commands[i] == "isEmpty") {
cout << (queue.isEmpty() ? "true" : "false") << " ";
} else if (commands[i] == "ArrayQueue") {
cout << "null ";
}
}
return 0;
}
Complexity Analysis:
- Time Complexity:
O(1)
as all individual queue operations are taking constant time. - Space Complexity:
O(1)
as the implementation uses a fixed size array.
2️⃣ Linked List Implementation of Queue
#include <iostream>
// Node structure for the linked list
struct Node {
int data; // Data part of the node
Node* next; // Pointer to the next node
// Constructor to initialize a node
Node(int val) : data(val), next(nullptr) {}
};
// Queue class using a linked list
class Queue {
private:
Node* front; // Pointer to the front of the queue
Node* rear; // Pointer to the rear of the queue
public:
// Constructor to initialize an empty queue
Queue() : front(nullptr), rear(nullptr) {}
// Function to add an element to the end of the queue
void enqueue(int x) {
Node* newNode = new Node(x);
if (rear == nullptr) {
// If queue is empty, both front and rear are the new node
front = rear = newNode;
} else {
// Otherwise, add the new node at the end and update rear
rear->next = newNode;
rear = newNode;
}
}
// Function to remove the element from the front of the queue
int dequeue() {
if (front == nullptr) {
std::cerr << "Queue is empty, cannot dequeue!" << std::endl;
return -1; // Indicate an error, or throw an exception
}
Node* temp = front; // Temporary pointer to hold the front node
int data = front->data; // Get the data of the front node
front = front->next; // Move front to the next node
if (front == nullptr) {
// If the queue is now empty, set rear to nullptr as well
rear = nullptr;
}
delete temp; // Free the memory of the old front node
return data; // Return the dequeued data
}
// Function to get the front element of the queue without removing it
int peek() {
if (front == nullptr) {
std::cerr << "Queue is empty, cannot peek!" << std::endl;
return -1; // Indicate an error, or throw an exception
}
return front->data;
}
// Function to check if the queue is empty
bool isEmpty() {
return front == nullptr;
}
// Destructor to clean up the memory allocated for the queue
~Queue() {
while (front != nullptr) {
Node* temp = front;
front = front->next;
delete temp;
}
}
};
// Example usage of the queue
int main() {
Queue q;
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
std::cout << "Front element is: " << q.peek() << std::endl; // Output: 10
std::cout << "Dequeued: " << q.dequeue() << std::endl; // Output: 10
std::cout << "Dequeued: " << q.dequeue() << std::endl; // Output: 20
std::cout << "Front element is: " << q.peek() << std::endl; // Output: 30
std::cout << "Is queue empty? " << (q.isEmpty() ? "Yes" : "No") << std::endl; // Output: No
q.dequeue(); // Remove the last element
std::cout << "Is queue empty? " << (q.isEmpty() ? "Yes" : "No") << std::endl; // Output: Yes
return 0;
}
Other Way:
#include <bits/stdc++.h>
using namespace std;
// Node structure
struct Node {
int val;
Node *next;
Node(int d) {
val = d;
next = NULL;
}
};
// Structure to represent stack
class LinkedListQueue {
private:
Node *start; // Start of the queue
Node *end; // End of the queue
int size; // Size of the queue
public:
// Constructor
LinkedListQueue() {
start = end = NULL;
size = 0;
}
// Method to push an element in the queue
void push(int x) {
// Creating a node
Node *element = new Node(x);
// If it is the first element being pushed
if(start == NULL) {
// Initialise the pointers
start = end = element;
}
else {
end->next = element; // Updating the pointers
end = element; // Updating the end
}
// Increment size by 1
size++;
}
// Method to pop an element from the queue
int pop() {
// If the queue is empty
if (start == NULL) {
return -1; // Pop operation cannot be performed
}
int value = start->val; // Get the front value
Node *temp = start; // Store the front temporarily
start = start->next; // Update front to next node
delete temp; // Delete old front node
size--; // Decrement size
return value; // Return data
}
// Method to get the front element in the queue
int peek() {
// If the stack is empty
if (start == NULL) {
return -1; // Top element cannot be accessed
}
return start->val; // Return the top
}
// Method to check if the queue is empty
bool isEmpty() {
return (size == 0);
}
};
int main() {
// Creating a queue
LinkedListQueue q;
// List of commands
vector<string> commands = {"LinkedListQueue", "push", "push",
"peek", "pop", "isEmpty"};
// List of inputs
vector<vector<int>> inputs = {{}, {3}, {7}, {}, {}, {}};
for (int i = 0; i < commands.size(); ++i) {
if (commands[i] == "push") {
q.push(inputs[i][0]);
cout << "null ";
} else if (commands[i] == "pop") {
cout << q.pop() << " ";
} else if (commands[i] == "peek") {
cout << q.peek() << " ";
} else if (commands[i] == "isEmpty") {
cout << (q.isEmpty() ? "true" : "false") << " ";
} else if (commands[i] == "LinkedListQueue") {
cout << "null ";
}
}
return 0;
}
Complexity Analysis:
- Time Complexity:
O(1)
because the time complexity for the push, pop, peek, size, and isEmpty() operations is O(1). - Space Complexity:
O(N)
where N is the number of elements of the stack.
Queue in STL
In the C++ Standard Template Library (STL), several types of queues are available, each with unique characteristics and uses. These queues are designed to handle various scenarios in data structures and algorithms. Here are the main types of queues provided by STL:
std::queue
: A standard FIFO (First In, First Out) queue.std::priority_queue
: A queue where elements are ordered by priority.std::deque
: A double-ended queue that allows insertion and deletion from both ends.
1️⃣ std:queue
:
The C++ Standard Template Library (STL) provides a queue
container that allows for easy implementation of a queue using a dynamic array. The queue
in STL is a part of the <queue>
header file and is designed to follow the First In, First Out (FIFO) principle.
#include <queue>
Queue Syntax:
queue<data_type> q;
Basic Operations of std::queue
The std::queue
provides several member functions to perform standard queue operations:
push()
: Adds an element to the back of the queue.pop()
: Removes the element from the front of the queue.front()
: Returns a reference to the front element of the queue.back()
: Returns a reference to the last element of the queue.empty()
: Checks if the queue is empty.size()
: Returns the number of elements in the queue.
Complete Example:
#include <iostream>
#include <queue>
int main() {
// Create a queue of integers
std::queue<int> q;
// Enqueue elements
q.push(10);
q.push(20);
q.push(30);
// Display size of the queue
std::cout << "Queue size: " << q.size() << std::endl;
// Access the front and back elements
std::cout << "Front element: " << q.front() << std::endl;
std::cout << "Back element: " << q.back() << std::endl;
// Dequeue elements
q.pop();
std::cout << "Front element after pop: " << q.front() << std::endl;
// Check if queue is empty
if (q.empty()) {
std::cout << "Queue is empty." << std::endl;
} else {
std::cout << "Queue is not empty." << std::endl;
}
return 0;
}
Queue size: 3
Front element: 10
Back element: 30
Front element after pop: 20
Queue is not empty.
2️⃣ std::priority_queue
:
std::priority_queue
is a container adapter that provides a priority-based queue, where the largest element is always at the front. This queue does not follow the FIFO principle but instead orders elements by their priority.
By Default: It stores the data in decreasing order.
- Header File:
<queue>
- Underlying Container: By default,
std::priority_queue
usesstd::vector
to store elements.
Basic Operations:
push(x)
: Inserts an elementx
into the priority queue.pop()
: Removes the element with the highest priority (the top element).top()
: Returns a reference to the top element (the highest priority element).empty()
: Checks if the priority queue is empty.size()
: Returns the number of elements in the priority queue.
Example:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> pq;
pq.push(5);
pq.push(1);
pq.push(10);
pq.push(3);
std::cout << "Top element: " << pq.top() << std::endl; // Should print 10
pq.pop();
std::cout << "Top element after pop: " << pq.top() << std::endl; // Should print 5
return 0;
}
Top element: 10
Top element after pop: 5
Note: By default, std::priority_queue
orders elements in descending order (max-heap). To create a min-heap, you can use a custom comparator or std::greater
with std::priority_queue
.
Min-Heap Example:
#include <iostream>
#include <queue>
#include <vector>
int main() {
// Min-heap priority queue
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
minHeap.push(5);
minHeap.push(1);
minHeap.push(10);
minHeap.push(3);
std::cout << "Top element: " << minHeap.top() << std::endl; // Should print 1
return 0;
}
Top element: 1
3️⃣ std::deque
(Double-Ended Queue)
std::deque
(double-ended queue) is a sequence container that allows fast insertion and deletion at both ends (front and back). Unlike std::queue
and std::priority_queue
, std::deque
is not a container adapter but a full-fledged container class.
- Header File:
<deque>
- Characteristics: Allows random access to elements, dynamic sizing, and provides constant-time insertions and deletions at both the front and the back.
Basic Operations:
push_back(x)
: Inserts an elementx
at the back.push_front(x)
: Inserts an elementx
at the front.pop_back()
: Removes the element at the back.pop_front()
: Removes the element at the front.front()
: Returns a reference to the first element.back()
: Returns a reference to the last element.empty()
: Checks if the deque is empty.size()
: Returns the number of elements in the deque.operator[]
orat()
: Accesses elements by index.
Example:
#include <iostream>
#include <deque>
int main() {
std::deque<int> d;
d.push_back(2);
d.push_front(1);
d.push_back(3);
d.push_front(0);
std::cout << "Deque elements: ";
for (int i : d) {
std::cout << i << " ";
}
std::cout << std::endl;
d.pop_back();
d.pop_front();
std::cout << "Deque elements after pop: ";
for (int i : d) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
Deque elements: 0 1 2 3
Deque elements after pop: 1 2