Introduction
Queues are an essential data structure used in computer science to manage data in a First-In-First-Out (FIFO) manner. A queue allows you to enqueue (add) elements at the rear and dequeue (remove) elements from the front, ensuring that the first element enqueued is the first one to be dequeued.
In this blog post, we will explore how to implement a queue in C using linked lists, a dynamic data structure that allows efficient insertion and deletion operations.
Remembering Linked Lists
A linked list is a collection of nodes, where each node contains both data and a pointer to the next node in the sequence. The last node's pointer is set to NULL, indicating the end of the list.
Creating the Queue Structure
To implement a queue using linked lists, we need to define two essential components: the Node structure of Linked Lists and the Queue structure.
typedef struct Node {
int data;
struct Node* next;
} Node;
typedef struct Queue {
Node* front;
Node* rear;
} Queue;
The Node
structure represents each element in the linked list, with a data
field to store the actual value and a next
pointer pointing to the next node in the list. The Queue
structure holds two pointers: front
points to the first element in the queue, and rear
points to the last element.
Initializing the Queue
We start by creating a function to initialize an empty queue:
Queue* createQueue() {
Queue* queue = (Queue*)malloc(sizeof(Queue));
if (queue == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
queue->front = queue->rear = NULL;
return queue;
}
The createQueue
function allocates memory for the queue, initializes both front
and rear
pointers to NULL, and returns the initialized queue.
Enqueuing Elements
The enqueue
function adds an element to the rear of the queue:
void enqueue(Queue* queue, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = NULL;
if (isEmpty(queue)) {
queue->front = queue->rear = newNode;
} else {
queue->rear->next = newNode;
queue->rear = newNode;
}
}
In this function, we allocate memory for a new node and set its data
field to the given value. If the queue is empty, both front
and rear
pointers point to the new node. Otherwise, we add the new node to the rear of the queue and update the rear
pointer accordingly.
Dequeuing Elements
The dequeue
function removes an element from the front of the queue:
int dequeue(Queue* queue) {
if (isEmpty(queue)) {
printf("Queue is empty. Cannot dequeue.\n");
exit(EXIT_FAILURE);
}
Node* temp = queue->front;
int data = temp->data;
queue->front = queue->front->next;
if (queue->front == NULL) {
queue->rear = NULL;
}
free(temp);
return data;
}
In this function, we first check if the queue is empty. If it is, we print an error message and terminate the program. Otherwise, we remove the front element from the queue, update the front
pointer to point to the next node, and free the memory used by the removed node. If the queue becomes empty after the dequeue operation, we update the rear
pointer to NULL as well.
Getting the Front Element
The front
function allows us to retrieve the front element without dequeuing it:
int front(Queue* queue) {
if (isEmpty(queue)) {
printf("Queue is empty.\n");
exit(EXIT_FAILURE);
}
return queue->front->data;
}
This function checks if the queue is empty and prints an error message if it is. Otherwise, it returns the data
field of the front node.
Freeing Memory
Finally, we provide a function to free the memory allocated for the queue and its nodes:
void freeQueue(Queue* queue) {
while (!isEmpty(queue)) {
dequeue(queue);
}
free(queue);
}
The freeQueue
function iteratively dequeues all elements from the queue until it becomes empty. And then, frees the memory allocated for the queue structure itself.
Full Code
#include <stdio.h>
#include <stdlib.h>
// Define the structure for a node in the linked list
typedef struct Node {
int data;
struct Node* next;
} Node;
// Define the structure for the queue
typedef struct Queue {
Node* front;
Node* rear;
} Queue;
// Function to create an empty queue
Queue* createQueue() {
Queue* queue = (Queue*)malloc(sizeof(Queue));
if (queue == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
queue->front = queue->rear = NULL;
return queue;
}
// Function to check if the queue is empty
int isEmpty(Queue* queue) {
return queue->front == NULL;
}
// Function to enqueue (add) an element to the queue
void enqueue(Queue* queue, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = NULL;
if (isEmpty(queue)) {
queue->front = queue->rear = newNode;
} else {
queue->rear->next = newNode;
queue->rear = newNode;
}
}
// Function to dequeue (remove) an element from the queue
int dequeue(Queue* queue) {
if (isEmpty(queue)) {
printf("Queue is empty. Cannot dequeue.\n");
exit(EXIT_FAILURE);
}
Node* temp = queue->front;
int data = temp->data;
queue->front = queue->front->next;
if (queue->front == NULL) {
queue->rear = NULL;
}
free(temp);
return data;
}
// Function to get the front element of the queue without dequeuing
int front(Queue* queue) {
if (isEmpty(queue)) {
printf("Queue is empty.\n");
exit(EXIT_FAILURE);
}
return queue->front->data;
}
// Function to free the memory allocated for the queue and its nodes
void freeQueue(Queue* queue) {
while (!isEmpty(queue)) {
dequeue(queue);
}
free(queue);
}
// Example usage
int main() {
Queue* queue = createQueue();
enqueue(queue, 10);
enqueue(queue, 20);
enqueue(queue, 30);
printf("Front element: %d\n", front(queue));
int dequeuedElement = dequeue(queue);
printf("Dequeued element: %d\n", dequeuedElement);
printf("Front element after dequeue: %d\n", front(queue));
freeQueue(queue);
return 0;
}
Conclusion
In this blog post, we explored the implementation of a queue in C using linked lists. Linked lists offer a dynamic and efficient way to handle data in a queue, allowing for easy insertion and deletion operations.
This is the (so far) last data structure implementation in the C Magic series. But if you wanna know more about queues check out my in-depth blog post, Unveiling the Power of Queues in Data Structures series.
Also, ping me on Twitter to have a chat about all this.
Happy coding!