Implementing a Headed Circular Doubly Linked List in C

Structural Definition

A headed circular doubly linked list utilizes a sentinel node (head) that acts as a starting point. Unlike a singly linked list, each node contains two pointers: prev pointing to the predecessor and next pointing to the successor. The sentinel node's prev points to the tail, and the tail's next points back to the sentinel, forming a closed loop. This structure allows for O(1) access to both the head and the tail of the list.

typedef int DataType;

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

Initialization

Initialization involves allocating the sentinel node. Since the list is initially empty, the sentinel node's pointers must reference itself to maintain the circular property.

CNode* ListInit() {
    CNode* pSentinel = (CNode*)malloc(sizeof(CNode));
    if (pSentinel == NULL) {
        perror("Memory allocation failed");
        exit(EXIT_FAILURE);
    }
    pSentinel->val = -1; // Sentinel data is typically unused
    pSentinel->prev = pSentinel;
    pSentinel->next = pSentinel;
    return pSentinel;
}

Node Creation Helper

Before performing insertions, a helper function is necessary to allocate new nodes and handle memory allocation errors.

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

Insertion Operations

The circular nature simplifies insertion logic, as the sentinel node provides immediate access to both ends of the list.

Head Insertion

To insert at the front, the new node is placed immediately after the sentinel node.

void ListPushFront(CNode* pSentinel, DataType x) {
    assert(pSentinel);
    CNode* newNode = CreateNode(x);
    
    newNode->next = pSentinel->next;
    newNode->prev = pSentinel;
    
    pSentinel->next->prev = newNode;
    pSentinel->next = newNode;
}

Tail Insertion

Inserting at the back requires linking the new node before the sentinel node. The sentinel's prev pointer acts as the tail pointer.

void ListPushBack(CNode* pSentinel, DataType x) {
    assert(pSentinel);
    CNode* newNode = CreateNode(x);
    
    newNode->prev = pSentinel->prev;
    newNode->next = pSentinel;
    
    pSentinel->prev->next = newNode;
    pSentinel->prev = newNode;
}

Deletion Operations

Deletion requires checks to ensure the list is not empty (i.e., the sentinel does not point to itself).

Head Deletion

The node immediately following the sentinel is removed.

void ListPopFront(CNode* pSentinel) {
    assert(pSentinel && pSentinel->next != pSentinel);
    CNode* target = pSentinel->next;
    
    target->next->prev = pSentinel;
    pSentinel->next = target->next;
    
    free(target);
    target = NULL;
}

Tail Deletion

The node immediately preceding the sentinel is removed.

void ListPopBack(CNode* pSentinel) {
    assert(pSentinel && pSentinel->prev != pSentinel);
    CNode* target = pSentinel->prev;
    
    target->prev->next = pSentinel;
    pSentinel->prev = target->prev;
    
    free(target);
    target = NULL;
}

Lookup and Positional Operations

Finding a node involves traversing from the sentinel's next until the loop returns to the sentinel.

CNode* ListFind(CNode* pSentinel, DataType x) {
    assert(pSentinel);
    CNode* current = pSentinel->next;
    while (current != pSentinel) {
        if (current->val == x) {
            return current;
        }
        current = current->next;
    }
    return NULL;
}

Insertion at Position

This function inserts a new node after a specified valid position.

void ListInsertAfter(CNode* pSentinel, CNode* pos, DataType x) {
    assert(pSentinel && pos);
    CNode* newNode = CreateNode(x);
    
    newNode->prev = pos;
    newNode->next = pos->next;
    
    pos->next->prev = newNode;
    pos->next = newNode;
}

Erasure at Position

This function unlinks a specific node from the chain and frees its memory.

void ListErase(CNode* pSentinel, CNode* pos) {
    assert(pSentinel && pos && pos != pSentinel);
    
    pos->prev->next = pos->next;
    pos->next->prev = pos->prev;
    
    free(pos);
    pos = NULL;
}

Memory Management

To prevent memory leaks, the list must be destroyed by iterating through all valid nodes and freeing them before freeing the sentinel.

void ListDestroy(CNode* pSentinel) {
    assert(pSentinel);
    CNode* current = pSentinel->next;
    while (current != pSentinel) {
        CNode* nextNode = current->next;
        free(current);
        current = nextNode;
    }
    free(pSentinel);
}

Tags: c programming Data Structures Linked List doubly linked list Memory Management

Posted on Sun, 17 May 2026 16:15:51 +0000 by Wolverine68