Understanding Doubly Linked Lists: Implementation and Operations in C

Introduction to Doubly Linked Lists

In a singly linked list, each node contains only a pointer too its successor, which creates a limitation: accessing a node's predecessor requires traversing the list from the beginning. This results in O(1) time compelxity for accessing the next node but O(n) for accessing the previous node. Doubly linked lists solve this problem by maintaining two pointers in each node.

A doubly linked list node contains two pointers: prev pointing to the predecessor and next pointing to the successor. The node structure can be defined as follows:

typedef int DataType;

typedef struct DListNode {
    DataType val;
    struct DListNode* next;
    struct DListNode* prev;
} DListNode;

Using DataType instead of directly using int provides flexibility for future type modifications.

Implementation

Creating a New Node

Before initialization, we need a function to allocate and create new nodes:

DListNode* CreateNode(DataType value) {
    DListNode* node = (DListNode*)malloc(sizeof(DListNode));
    if (node == NULL) {
        perror("Memory allocation failed");
        exit(EXIT_FAILURE);
    }
    
    node->val = value;
    node->next = NULL;
    node->prev = NULL;
    return node;
}

List Initialization

The initialization creates a sentinel (head) node that points to itself, forming a circular structure:

DListNode* ListInit() {
    DListNode* head = CreateNode(-1);
    head->next = head;
    head->prev = head;
    return head;
}

The sentinel node must point to itself; otherwise, the pointers would be NULL, breaking the circular structure.

Printing the List

void ListPrint(DListNode* head) {
    assert(head != NULL);
    
    DListNode* current = head->next;
    while (current != head) {
        printf("%d -> ", current->val);
        current = current->next;
    }
    printf("NULL\n");
}

The loop condition current != head prevents infinite iteration since the list is circular.

Ensertion Operations

Tail Insertion:

void ListPushBack(DListNode* head, DataType value) {
    assert(head != NULL);
    
    DListNode* newNode = CreateNode(value);
    DListNode* last = head->prev;
    
    // Link new node between last and head
    last->next = newNode;
    newNode->prev = last;
    newNode->next = head;
    head->prev = newNode;
}

Head Insertion:

void ListPushFront(DListNode* head, DataType value) {
    assert(head != NULL);
    
    DListNode* newNode = CreateNode(value);
    DListNode* first = head->next;
    
    // Link new node between head and first
    newNode->next = first;
    newNode->prev = head;
    head->next = newNode;
    first->prev = newNode;
}

Deletion Operations

Head Deletion:

void ListPopFront(DListNode* head) {
    assert(head != NULL);
    assert(head->next != head);  // List is not empty
    
    DListNode* target = head->next;
    DListNode* newFirst = target->next;
    
    head->next = newFirst;
    newFirst->prev = head;
    
    free(target);
}

Tail Deletion:

void ListPopBack(DListNode* head) {
    assert(head != NULL);
    assert(head->next != head);
    
    DListNode* target = head->prev;
    DListNode* newLast = target->prev;
    
    newLast->next = head;
    head->prev = newLast;
    
    free(target);
}

Insertion at Arbitrary Position

void ListInsert(DListNode* position, DataType value) {
    assert(position != NULL);
    
    DListNode* newNode = CreateNode(value);
    DListNode* previous = position->prev;
    
    // Insert before position
    previous->next = newNode;
    newNode->prev = previous;
    newNode->next = position;
    position->prev = newNode;
}

Deletion at Arbitrary Position

void ListErase(DListNode* position) {
    assert(position != NULL);
    
    DListNode* before = position->prev;
    DListNode* after = position->next;
    
    before->next = after;
    after->prev = before;
    
    free(position);
}

Finding an Element

DListNode* ListFind(DListNode* head, DataType value) {
    assert(head != NULL);
    
    DListNode* current = head->next;
    while (current != head) {
        if (current->val == value) {
            return current;
        }
        current = current->next;
    }
    return NULL;  // Not found
}

Destroying the List

void ListDestroy(DListNode* head) {
    assert(head != NULL);
    
    DListNode* current = head->next;
    while (current != head) {
        DListNode* temp = current->next;
        free(current);
        current = temp;
    }
    free(head);
}

Complete Header File

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int DataType;

typedef struct DListNode {
    DataType val;
    struct DListNode* next;
    struct DListNode* prev;
} DListNode;

// Initialization
DListNode* ListInit();

// Node creation
DListNode* CreateNode(DataType value);

// Print
void ListPrint(DListNode* head);

// Insertion
void ListPushBack(DListNode* head, DataType value);
void ListPushFront(DListNode* head, DataType value);
void ListInsert(DListNode* position, DataType value);

// Deletion
void ListPopFront(DListNode* head);
void ListPopBack(DListNode* head);
void ListErase(DListNode* position);

// Search
DListNode* ListFind(DListNode* head, DataType value);

// Cleanup
void ListDestroy(DListNode* head);

Example Usage

#include "DList.h"

int main() {
    DListNode* list = ListInit();
    
    ListPushBack(list, 10);
    ListPushBack(list, 20);
    ListPushBack(list, 30);
    ListPushFront(list, 5);
    
    ListPrint(list);  // Output: 5 -> 10 -> 20 -> 30 -> NULL
    
    DListNode* found = ListFind(list, 20);
    if (found != NULL) {
        ListErase(found);
    }
    
    ListPrint(list);  // Output: 5 -> 10 -> 30 -> NULL
    
    ListDestroy(list);
    return 0;
}

Tags: doubly linked list Data Structures c programming Memory Management linked list operations

Posted on Fri, 12 Jun 2026 18:01:26 +0000 by HUWUWA