C Programming Data Structures Deque And Priority Queue Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    9 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of C Programming data structures Deque and Priority Queue


Deque (Double-ended Queue)

A Deque, short for Double-ended Queue, is a dynamic data structure that allows insertion and deletion of elements from both ends efficiently. It combines the properties of both stacks and queues, offering more flexibility compared to traditional queues.

Properties

  • Dynamic Size: You can add or remove elements without concern for predefined limits.
  • Insertion/Deletion: Allows operations at both front and back ends.
  • Random Access: Accessing elements is typically done from the ends, not in the middle.

Operations

  1. Push Front: Adds an element to the front of the deque.
  2. Push Back: Adds an element to the back of the deque.
  3. Pop Front: Removes and returns the front element.
  4. Pop Back: Removes and returns the back element.
  5. Front: Retrieves but does not remove the front element.
  6. Back: Retrieves but does not remove the back element.
  7. Empty: Checks if the deque is empty.
  8. Size: Returns the number of elements in the deque.

Implementation in C

To implement a deque in C, you can use a doubly linked list where each node contains a data field and pointers to the next and previous nodes. Alternatively, you can use a circular array for a more compact implementation at the cost of some complexity in managing indices.

Doubly Linked List Implementation:

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

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

struct Deque {
    struct Node* front;
    struct Node* rear;
    int count; // Keeps track of elements in the deque
};

void initializeDeque(struct Deque* dq) {
    dq->front = NULL;
    dq->rear = NULL;
    dq->count = 0;
}

void pushFront(struct Deque* dq, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    if (!newNode) return; // Memory allocation failed
    newNode->data = data;
    newNode->next = dq->front;
    newNode->prev = NULL;

    if (dq->front == NULL) {
        dq->front = dq->rear = newNode;
    } else {
        dq->front->prev = newNode;
        dq->front = newNode;
    }
    dq->count++;
}

void pushBack(struct Deque* dq, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    if (!newNode) return; // Memory allocation failed
    newNode->data = data;
    newNode->next = NULL;
    newNode->prev = dq->rear;

    if (dq->rear == NULL) {
        dq->front = dq->rear = newNode;
    } else {
        dq->rear->next = newNode;
        dq->rear = newNode;
    }
    dq->count++;
}

int popFront(struct Deque* dq) {
    if (dq->front == NULL) {
        printf("Deque Underflow\n");
        return -1;
    }

    struct Node* temp = dq->front;
    int data = temp->data;
    
    if (dq->front == dq->rear) {
        dq->front = dq->rear = NULL;
    } else {
        dq->front = dq->front->next;
        dq->front->prev = NULL;
    }
    free(temp);
    dq->count--;
    return data;
}

int popBack(struct Deque* dq) {
    if (dq->rear == NULL) {
        printf("Deque Underflow\n");
        return -1;
    }

    struct Node* temp = dq->rear;
    int data = temp->data;

    if (dq->front == dq->rear) {
        dq->front = dq->rear = NULL;
    } else {
        dq->rear = dq->rear->prev;
        dq->rear->next = NULL;
    }
    free(temp);
    dq->count--;
    return data;
}

int main() {
    struct Deque dq;
    initializeDeque(&dq);

    pushFront(&dq, 10);
    pushBack(&dq, 20);
    pushFront(&dq, 5);

    printf("Popped from front: %d\n", popFront(&dq));
    printf("Popped from back: %d\n", popBack(&dq));

    return 0;
}

Advantages

  • Efficiency: Allows O(1) time complexity for insertions and deletions at both ends.
  • Flexibility: Ideal for scenarios requiring flexible access to elements from either end.

Applications

  • Undo/Redo Mechanisms: In text editors and software applications.
  • Breadth-first Search (BFS): Utilized in graph algorithms.
  • Sliding Window Algorithms: Used for maintaining windows like sums or other properties over arrays or lists.

Priority Queue

A Priority Queue is a specialized data structure that manages a collection of elements, each associated with a priority value. Elements are served based on their priority, with higher-priority elements typically being served before lower-priority ones.

Properties

  • Ordered by Priority: Higher-priority elements take precedence.
  • Heap-Based Implementation: Common to achieve efficient operations.
  • Dynamic Size: Elements can be added or removed as needed.

Types

  • Max-Priority Queue: The element with the highest priority is served first.
  • Min-Priority Queue: The element with the lowest priority is served first.

Operations

  1. Insert: Adds an element to the queue.
  2. Peek/Find Max/Min: Retrieves the element with the highest/lowest priority without removing it.
  3. Extract Max/Min: Removes and returns the element with the highest/lowest priority.
  4. Delete: Removes a specific element, often accompanied by heap restructuring.
  5. Change Priority: Adjusts the priority of an existing element, may involve restructuring.
  6. Empty: Checks if the priority queue is empty.
  7. Size: Returns the number of elements in the priority queue.

Implementation in C using Binary Heap

A binary heap is commonly used to implement a priority queue efficiently. The root of the heap holds the maximum (or minimum) element depending on the type.

Max-Priority Queue Implementation Using Binary Heap:

#include <stdio.h>
#include <limits.h>

struct PriorityQueue {
    int capacity;
    int size;
    int* elements;
};

struct PriorityQueue* createPriorityQueue(int capacity) {
    struct PriorityQueue* pq = (struct PriorityQueue*)malloc(sizeof(struct PriorityQueue));
    pq->capacity = capacity;
    pq->size = 0;
    pq->elements = (int*)malloc(capacity * sizeof(int));
    return pq;
}

void swap(int* a, int* b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int getParentIndex(int index) { return (index - 1) / 2; }
int getLeftChildIndex(int index) { return 2 * index + 1; }
int getRightChildIndex(int index) { return 2 * index + 2; }

void heapifyUp(struct PriorityQueue* pq, int index) {
    int parentIndex = getParentIndex(index);
    while (index > 0 && pq->elements[index] > pq->elements[parentIndex]) {
        swap(&pq->elements[index], &pq->elements[parentIndex]);
        index = parentIndex;
        parentIndex = getParentIndex(index);
    }
}

void heapifyDown(struct PriorityQueue* pq, int index) {
    int left = getLeftChildIndex(index), right = getRightChildIndex(index);
    int largest = index;

    if (left < pq->size && pq->elements[left] > pq->elements[largest]) largest = left;
    if (right < pq->size && pq->elements[right] > pq->elements[largest]) largest = right;

    if (largest != index) {
        swap(&pq->elements[index], &pq->elements[largest]);
        heapifyDown(pq, largest);
    }
}

void insert(struct PriorityQueue* pq, int data) {
    if (pq->size >= pq->capacity) return; // Priority Queue Overflow
    pq->elements[pq->size++] = data;
    heapifyUp(pq, pq->size - 1);
}

int peek(struct PriorityQueue* pq) {
    if (pq->size <= 0) {
        printf("Priority Queue is empty\n");
        return INT_MIN;
    }
    return pq->elements[0];
}

int extractMax(struct PriorityQueue* pq) {
    if (pq->size <= 0) {
        printf("Priority Queue is empty\n");
        return INT_MIN;
    }

    int maxItem = pq->elements[0];
    pq->elements[0] = pq->elements[pq->size - 1];
    pq->size--;

    heapifyDown(pq, 0);
    return maxItem;
}

int main() {
    struct PriorityQueue* pq = createPriorityQueue(10);

    insert(pq, 10);
    insert(pq, 20);
    insert(pq, 30);

    printf("Max element is: %d\n", peek(pq));
    printf("Extracted Max: %d\n", extractMax(pq));

    return 0;
}

Advantages

  • Efficiency: Insertion, deletion, and retrieval of the highest/lowest priority element are efficient (O(log n)).
  • Simplicity: Simple using binary heaps, which are inherently well-suited for these operations.
  • Applications in Scheduling: Common in operating systems for task management.

Disadvantages

  • Complexity: Maintaining the heap property during insertions and deletions can be complex.
  • Random Access Limited: Direct access to any element is not easily possible.

Applications

  • Event-driven Systems: Such as simulations and GUI libraries.
  • Dijkstra's Algorithm: For finding shortest paths in graphs.
  • Huffman Coding: Used in efficient data compression techniques.

Use Case Comparison

  • Deque:

    • When you need a data structure that supports efficient insertion and deletion from both ends.
    • Useful in rolling windows, undo mechanisms, and BFS-like scenarios where two-way access benefits performance.
  • Priority Queue:

    • When tasks or events need to be processed based on priority.
    • Useful in scheduling problems, pathfinding algorithms (like Dijkstra), and resource allocation scenarios.

By understanding these data structures and their implementations in C, you can effectively manage various computational requirements, optimizing code performance in scenarios where deque and priority queue behaviors are advantageous.


Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement C Programming data structures Deque and Priority Queue

Deque (Double-ended Queue)

A deque, or double-ended queue, allows insertion and deletion of elements at both ends. Below is a step-by-step guide to implementing a simple deque in C.

Step 1: Define the Deque Structure

First, we need to define the structure for the deque and its nodes.

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

// Node structure for deque
struct Node {
    int data;
    struct Node* next;
    struct Node* prev;
};

// Deque structure
struct Deque {
    struct Node* front;
    struct Node* rear;
};

Step 2: Create Helper Functions

Helper functions for creating a new node and initializing the deque.

// Function to create a new node
struct Node* createNode(int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = NULL;
    newNode->prev = NULL;
    return newNode;
}

// Function to initialize deque
struct Deque* createDeque() {
    struct Deque* deque = (struct Deque*)malloc(sizeof(struct Deque));
    deque->front = NULL;
    deque->rear = NULL;
    return deque;
}

Step 3: Implement Deque Operations

Implement functions to add and remove elements from both ends.

// Function to insert element at the front
void insertFront(struct Deque* deque, int data) {
    struct Node* newNode = createNode(data);
    if (deque->front == NULL) {
        deque->front = newNode;
        deque->rear = newNode;
    } else {
        newNode->next = deque->front;
        deque->front->prev = newNode;
        deque->front = newNode;
    }
}

// Function to insert element at the rear
void insertRear(struct Deque* deque, int data) {
    struct Node* newNode = createNode(data);
    if (deque->rear == NULL) {
        deque->front = newNode;
        deque->rear = newNode;
    } else {
        newNode->prev = deque->rear;
        deque->rear->next = newNode;
        deque->rear = newNode;
    }
}

// Function to delete element from the front
void deleteFront(struct Deque* deque) {
    if (deque->front == NULL) {
        printf("Deque is empty.\n");
        return;
    }
    struct Node* temp = deque->front;
    deque->front = deque->front->next;
    if (deque->front != NULL) {
        deque->front->prev = NULL;
    } else {
        deque->rear = NULL;
    }
    free(temp);
}

// Function to delete element from the rear
void deleteRear(struct Deque* deque) {
    if (deque->rear == NULL) {
        printf("Deque is empty.\n");
        return;
    }
    struct Node* temp = deque->rear;
    deque->rear = deque->rear->prev;
    if (deque->rear != NULL) {
        deque->rear->next = NULL;
    } else {
        deque->front = NULL;
    }
    free(temp);
}

// Function to get the front element
int getFront(struct Deque* deque) {
    if (deque->front == NULL) {
        printf("Deque is empty.\n");
        return -1;
    }
    return deque->front->data;
}

// Function to get the rear element
int getRear(struct Deque* deque) {
    if (deque->rear == NULL) {
        printf("Deque is empty.\n");
        return -1;
    }
    return deque->rear->data;
}

// Function to check if the deque is empty
int isEmpty(struct Deque* deque) {
    return (deque->front == NULL);
}

Step 4: Test the Deque

Write a main function to test the deque operations.

int main() {
    struct Deque* deque = createDeque();
    
    insertFront(deque, 5);
    insertRear(deque, 10);
    insertFront(deque, 15);
    insertRear(deque, 20);
    
    printf("Front: %d\n", getFront(deque)); // Output: 15
    printf("Rear: %d\n", getRear(deque));   // Output: 20
    
    deleteFront(deque);
    deleteRear(deque);
    
    printf("Front after deletion: %d\n", getFront(deque)); // Output: 5
    printf("Rear after deletion: %d\n", getRear(deque));   // Output: 10
    
    return 0;
}

Priority Queue

A priority queue is a special type of queue where each element also has a priority assigned to it. Elements are served based on their priority.

For simplicity, we'll implement a min-priority queue using an array.

Step 1: Define the Priority Queue Structure

Define the structure for the priority queue.

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

#define MAX 100

struct PriorityQueue {
    int data[MAX];
    int size;
};

// Initialize PQ
void initializePQ(struct PriorityQueue* pq) {
    pq->size = 0;
}

// Function to swap two elements
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Function to heapify up
void heapifyUp(struct PriorityQueue* pq, int index) {
    while (index != 0 && pq->data[(index-1)/2] > pq->data[index]) {
        swap(&pq->data[index], &pq->data[(index-1)/2]);
        index = (index-1)/2;
    }
}

// Function to heapify down
void heapifyDown(struct PriorityQueue* pq, int index) {
    int smallest = index;
    int left = 2*index + 1;
    int right = 2*index + 2;
    
    if (left < pq->size && pq->data[left] < pq->data[smallest])
        smallest = left;
    
    if (right < pq->size && pq->data[right] < pq->data[smallest])
        smallest = right;
    
    if (smallest != index) {
        swap(&pq->data[index], &pq->data[smallest]);
        heapifyDown(pq, smallest);
    }
}

// Function to insert element in PQ
void insertPQ(struct PriorityQueue* pq, int data) {
    if (pq->size >= MAX) {
        printf("Priority Queue is full.\n");
        return;
    }
    
    pq->data[pq->size] = data;
    pq->size++;
    heapifyUp(pq, pq->size - 1);
}

// Function to extract the minimum element
int extractMin(struct PriorityQueue* pq) {
    if (pq->size <= 0) {
        printf("Priority Queue is empty.\n");
        return -1;
    }
    if (pq->size == 1) {
        pq->size--;
        return pq->data[0];
    }
    
    int root = pq->data[0];
    pq->data[0] = pq->data[pq->size - 1];
    pq->size--;
    heapifyDown(pq, 0);
    
    return root;
}

// Function to get the minimum element
int getMin(struct PriorityQueue* pq) {
    if (pq->size <= 0) {
        printf("Priority Queue is empty.\n");
        return -1;
    }
    return pq->data[0];
}

// Function to check if PQ is empty
int isEmptyPQ(struct PriorityQueue* pq) {
    return (pq->size == 0);
}

Step 2: Test the Priority Queue

Write a main function to test the priority queue operations.

Top 10 Interview Questions & Answers on C Programming data structures Deque and Priority Queue

Deque (Double Ended Queue)

1. What is a Deque in C Programming?

Answer: A Deque, or Double Ended Queue, is a linear data structure that allows insertions and deletions at both ends. In C, a Deque can be implemented using arrays or linked lists. Deques allow faster operations when compared to traditional stacks and queues due to their flexibility in adding or removing elements from either end.

2. How do you insert an element at the front of a Deque?

Answer: To insert an element at the front of a Deque, you need to shift all the elements one position to the right and then add the new element at position 0. Using a linked list, you can achieve this by creating a new node and adjusting pointers. Here’s a pseudo-code for array-based approach:

if (deque->front == 0) {
    // handle full deque case
}
else {
    deque->front = (deque->front + deque->size - 1) % deque->capacity;
    deque->arr[deque->front] = item;
}

3. How do you insert an element at the rear of a Deque?

Answer: Inserting an element at the rear is straightforward. Just need to add the element at the rear end and increment the rear pointer. Again, for linked lists, just attach a new node at the tail.

if (deque->rear == deque->capacity) {
    // handle full deque
}
else {
    deque->arr[deque->rear++] = item;
}

4. What are the operations supported by a Deque?

Answer: Key operations in a Deque include:

  • insertFront(item): Add an item at the front.
  • insertRear(item): Add an item at the rear.
  • deleteFront(): Remove an item from the front.
  • deleteRear(): Remove an item from the rear.
  • getFront(): Retrieve the front item.
  • getRear(): Retrieve the rear item.
  • isValid(): Check if the Deque is not empty.

5. How would you implement a circular Deque in C?

Answer: A circular Deque makes use of the same underlying array multiple times. We maintain two indices, front and rear, and both are circularly incremented or decremented to wrap around the array.

// example function to insert at rear
void insertRear(int* arr, int* front, int* rear, int capacity, int item) {
    if ((*rear + 1) % capacity == *front)
        printf("Deque is full\n");
    *rear = (*rear + 1) % capacity;
    arr[*rear] = item;
}

Priority Queue

6. What is a priority queue?

Answer: A priority queue is a special type of queue where each element has a priority assigned to it. Elements are served based on their priority and not in a FIFO (First In First Out) manner. In C, priority queues can be implemented using arrays, linked lists, or heap data structures for efficient implementation.

7. How do you implement a priority queue using a heap?

Answer: A priority queue can be efficiently implemented using a binary heap (usually a max-heap for max priority queue). In a max-heap, the element at the root of the tree has the highest priority.

typedef struct {
    int *arr;
    int capacity, size;
} PriorityQueue;

// Example swap function
void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

// Function to get parent index
int getParent(int i) {
    return (i - 1) / 2;
}

// Insert into the priority queue
void insert(PriorityQueue *pq, int item) {
    pq->arr[pq->size++] = item;
    // Heapify up
    for (int i = pq->size - 1; i != 0 && pq->arr[getParent(i)] < pq->arr[i]; ) {
        swap(&pq->arr[getParent(i)], &pq->arr[i]);
        i = getParent(i);
    }
}

// Heapify down
void heapify(PriorityQueue *pq, int i) {
    int largest = i, l = 2 * i + 1, r = 2 * i + 2;

    if (l < pq->size && pq->arr[l] > pq->arr[largest])
        largest = l;
    if (r < pq->size && pq->arr[r] > pq->arr[largest])
        largest = r;

    if (largest != i) {
        swap(&pq->arr[i], &pq->arr[largest]);
        heapify(pq, largest);
    }
}

// Extract maximum element (highest priority)
int extractMax(PriorityQueue *pq) {
    if (pq->size <= 0)
        return INT_MIN;

    if (pq->size == 1) {
        pq->size--;
        return pq->arr[0];
    }

    int root = pq->arr[0];
    pq->arr[0] = pq->arr[pq->size - 1];
    pq->size = pq->size - 1;
    heapify(pq, 0);
    return root;
}

8. What are the applications of priority queues?

Answer: Priority queues are used in various real-world applications such as:

  • Dijkstra's Algorithm for finding shortest path.
  • Task Scheduling.
  • CPU Scheduling.
  • Huffman Coding Algorithm.

9. Can a priority queue be implemented using a linked list?

Answer: Yes, a priority queue can be implemented using a linked list. Sorting the linked list based on priority ensures quick removal of the highest priority element, but insertions and deletions may take more time compared to implementations using binary heaps or balanced trees.

10. What is a binary heap and how is it different from a regular complete binary tree?

Answer: A binary heap is a specialized binary tree that has all levels filled except possibly for the last level, which is filled from left to right. A heap can be either a max-heap or a min-heap. In a max-heap, the key at the root must be the largest among all keys present in the binary heap. The same property must be recursively true for all nodes in the binary heap. A min-heap is analogous to a max-heap except the key at the root must be the minimum among all keys.

You May Like This Related .NET Topic

Login to post a comment.