Linked List-based Queue in C

Optimizing Data Management with Queues

·

5 min read

Linked List-based Queue in C

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!