C Program to Implement Deque in C

C Program to Implement Deque in C

A deque (double-ended queue) is a linear data structure that allows insertion and deletion of elements from both ends: front and rear. This flexibility makes it more versatile than a standard queue, and deques are widely used in task scheduling, caching, and sliding window algorithms. This article demonstrates array-based and linked-list-based deque implementations in C, including extended operations like getFront, getRear, isEmpty, size, and erase.

Understanding the Problem

A deque supports operations at both ends while maintaining element order. The challenge is correctly managing the front and rear pointers, especially in circular arrays or dynamically allocated linked lists. Proper handling prevents overflow, underflow, and invalid memory access. Utility functions such as getFront and getRear allow inspecting elements without removing them, while size and erase provide additional flexibility for managing the deque.

Program 1: Array-Based Deque

The array-based implementation uses a fixed-size array and modulo arithmetic to handle wrap-around. Insertion and deletion can be performed at both ends efficiently. Overflow and underflow are checked to prevent invalid operations, and the utility functions allow easy inspection and management of the deque.

#include <stdio.h>
#define MAX 5

int deque[MAX];
int front = -1, rear = -1;

int isEmpty() {
    return front == -1;
}

int isFull() {
    return (front == 0 && rear == MAX - 1) || (front == rear + 1);
}

void insertFront(int value) {

    if (isFull()) {
        printf("Deque Overflow! Cannot insert %d\n", value);
        return;
    }

    if (isEmpty())
        front = rear = 0;
    else if (front == 0)
        front = MAX - 1;
    else
        front--;

    deque[front] = value;

    printf("%d inserted at front\n", value);

}

void insertRear(int value) {

    if (isFull()) {
        printf("Deque Overflow! Cannot insert %d\n", value);
        return;
    }

    if (isEmpty())
        front = rear = 0;
    else
        rear = (rear + 1) % MAX;

    deque[rear] = value;

    printf("%d inserted at rear\n", value);

}

void deleteFront() {

    if (isEmpty()) {
        printf("Deque Underflow! Nothing to delete.\n");
        return;
    }

    printf("%d deleted from front\n", deque[front]);

    if (front == rear)
        front = rear = -1;
    else
        front = (front + 1) % MAX;

}

void deleteRear() {

    if (isEmpty()) {
        printf("Deque Underflow! Nothing to delete.\n");
        return;
    }

    printf("%d deleted from rear\n", deque[rear]);

    if (front == rear)
        front = rear = -1;
    else if (rear == 0)
        rear = MAX - 1;
    else
        rear--;

}

int getFront() {

    if (isEmpty()) {
        printf("Deque is empty\n");
        return -1;
    }

    return deque[front];

}

int getRear() {

    if (isEmpty()) {
        printf("Deque is empty\n");
        return -1;
    }

    return deque[rear];

}

int size() {

    if (isEmpty())
        return 0;
    if (rear >= front)
        return rear - front + 1;

    return MAX - (front - rear - 1);

}

void erase() {
    front = rear = -1;
    printf("Deque cleared\n");
}

void display() {

    if (isEmpty()) {
        printf("Deque is empty\n");
        return;
    }

    int i = front;
    printf("Deque elements: ");

    while (1) {

        printf("%d ", deque[i]);

        if (i == rear) break;
        i = (i + 1) % MAX;

    }

    printf("\n");

}

int main() {

    insertRear(10);
    insertRear(20);
    insertFront(5);
    display();

    printf("Front: %d, Rear: %d, Size: %d\n", getFront(), getRear(), size());

    deleteFront();
    display();

    insertRear(30);
    insertFront(1);
    display();

    printf("Front: %d, Rear: %d, Size: %d\n", getFront(), getRear(), size());

    erase();
    display();

    return 0;

}

The array-based deque efficiently handles insertions and deletions at both ends while allowing quick access to the front and rear elements. The size and erase functions provide further convenience for managing the deque’s contents.

Program 2: Linked-List-Based Deque

The linked-list implementation allows dynamic memory allocation and unlimited size. Each node contains data and pointers to the previous and next elements, enabling efficient insertions and deletions at both ends. Utility functions provide inspection and management without removing elements, and the erase function clears the entire deque safely.

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *prev;
    struct Node *next;
} Node;

Node *front = NULL, *rear = NULL;

int isEmpty() {
    return front == NULL;
}

void insertFront(int value) {

    Node *newNode = (Node*)malloc(sizeof(Node));
    newNode->data = value;
    newNode->prev = NULL;
    newNode->next = front;

    if (!front)
        rear = newNode;
    else
        front->prev = newNode;

    front = newNode;

    printf("%d inserted at front\n", value);

}

void insertRear(int value) {

    Node *newNode = (Node*)malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = NULL;
    newNode->prev = rear;

    if (!rear)
        front = newNode;
    else
        rear->next = newNode;

    rear = newNode;

    printf("%d inserted at rear\n", value);

}

void deleteFront() {

    if (isEmpty()) {
        printf("Deque Underflow! Nothing to delete.\n");
        return;
    }

    Node *temp = front;

    printf("%d deleted from front\n", front->data);

    front = front->next;

    if (front)
        front->prev = NULL;
    else
        rear = NULL;

    free(temp);

}

void deleteRear() {

    if (isEmpty()) {
        printf("Deque Underflow! Nothing to delete.\n");
        return;
    }

    Node *temp = rear;

    printf("%d deleted from rear\n", rear->data);

    rear = rear->prev;

    if (rear)
        rear->next = NULL;
    else
        front = NULL;

    free(temp);

}

int getFront() {

    if (isEmpty()) {
        printf("Deque is empty\n");
        return -1;
    }

    return front->data;

}

int getRear() {

    if (isEmpty()) {
        printf("Deque is empty\n");
        return -1;
    }

    return rear->data;

}

int size() {

    int count = 0;
    Node *temp = front;

    while (temp) {
        count++;
        temp = temp->next;
    }

    return count;

}

void erase() {

    Node *temp;

    while (front) {

        temp = front;
        front = front->next;
        free(temp);

    }

    rear = NULL;

    printf("Deque cleared\n");

}

void display() {

    if (isEmpty()) {
        printf("Deque is empty\n");
        return;
    }

    Node *temp = front;

    printf("Deque elements: ");

    while (temp) {
        printf("%d ", temp->data);
        temp = temp->next;
    }

    printf("\n");

}

int main() {

    insertRear(10);
    insertRear(20);
    insertFront(5);
    display();

    printf("Front: %d, Rear: %d, Size: %d\n", getFront(), getRear(), size());

    deleteFront();
    display();

    insertRear(30);
    insertFront(1);
    display();

    printf("Front: %d, Rear: %d, Size: %d\n", getFront(), getRear(), size());

    erase();
    display();

    return 0;

}

The linked-list deque allows unlimited growth while keeping insertion and deletion operations efficient. The added utility functions make it easy to inspect and manage elements without modifying the deque.

FAQs

Q1: What is the time complexity of operations in a deque?
Insertion and deletion at both ends are O(1) for both array and linked-list implementations.

Q2: When should a linked-list deque be preferred over an array deque?
Use linked lists when the maximum size is unknown or dynamic resizing is needed. Arrays are simpler for fixed-size deques.

Q3: Can a deque be used as a stack or queue?
Yes, a deque can simulate a stack (LIFO) or a queue (FIFO) by restricting operations to one end or using both ends strategically.

Q4: What are common applications of deques?
Deques are used in sliding window problems, undo/redo operations in editors, and cache implementations (like LRU cache).

Conclusion

Deques are highly versatile data structures that support operations at both ends. Array-based deques are efficient for small fixed-size applications, while linked-list deques provide dynamic sizing and unlimited growth. Proper pointer management and memory handling ensure that deque operations are performed safely and efficiently. The extended operations getFront, getRear, isEmpty, size, and erase enhance usability, making it easy to manage deque contents in practical applications.

References & Additional Resources

A curated collection of textbooks and tutorials for learning deque (double-ended queue) implementations in C.

  1. Kernighan, B. W., & Ritchie, D. M. (1988). The C Programming Language (2nd Edition). Prentice Hall – Classic reference covering C fundamentals, arrays, pointers, and memory management essential for deque implementations.
  2. Thareja, R. (2011). Data Structures Using C. Oxford University Press – Comprehensive coverage of deque concepts, including array- and linked-list-based implementations with practical examples.
  3. GeeksforGeeks: Deque Using Arrays in C – Step-by-step explanation and code for implementing deques using arrays.
  4. GeeksforGeeks: Deque Using Linked List – Guide for implementing deques using doubly linked lists.
  5. Programiz: Queue and Deque Data Structures – Practical guide and C examples for deque operations.
  6. C Standard Library Documentation – Official reference for standard C functions and libraries often used in deque operations.

Scroll to Top