Doubly linked lists consist of nodes that contain both data and references to adjacent nodes. Each node maintains pointers to both the next and previous elements, enabling bidirectional traversal. The fundamental node structure is defined as:
typedef int ListItemType;
typedef struct ListNode {
ListItemType value;
struct ListNode* next_node;
struct ListNode* previous_node;
} ListNode;
Initialization requires creating a sentinel node that forms the circular structure's foundation. The initialization process establishes a self-referential sentinel:
ListNode* createListNode(ListItemType value) {
ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
if (!new_node) {
perror("Memory allocation error");
exit(EXIT_FAILURE);
}
new_node->value = value;
new_node->next_node = new_node;
new_node->previous_node = new_node;
return new_node;
}
void initializeList(ListNode** head_ptr) {
*head_ptr = createListNode(-1);
}
Traversal leverages the sentinel node as a termination marker. The printing function demonstrates circular traversal:
void displayList(ListNode* head) {
ListNode* current = head->next_node;
while (current != head) {
printf("%d->", current->value);
current = current->next_node;
}
printf("\n");
}
Insertion operations maintain circular integrity through pointer adjustments. The tail insertion process connects new nodes to the list's endpoints:
void appendNode(ListNode* head, ListItemType value) {
assert(head);
ListNode* new_node = createListNode(value);
new_node->previous_node = head->previous_node;
new_node->next_node = head;
head->previous_node->next_node = new_node;
head->previous_node = new_node;
}
Head insertion modifies references between the sentinel and first data node:
void prependNode(ListNode* head, ListItemType value) {
assert(head);
ListNode* new_node = createListNode(value);
new_node->next_node = head->next_node;
new_node->previous_node = head;
head->next_node->previous_node = new_node;
head->next_node = new_node;
}
Deletion operations require pointer realignment before memory deallocation. Tail removal updates adjacent node references:
void removeLast(ListNode* head) {
assert(head && head->next_node != head);
ListNode* target = head->previous_node;
target->previous_node->next_node = head;
head->previous_node = target->previous_node;
free(target);
}
Head deletion follows a similar reference update pattern:
void removeFirst(ListNode* head) {
assert(head && head->next_node != head);
ListNode* target = head->next_node;
head->next_node = target->next_node;
target->next_node->previous_node = head;
free(target);
}
Position-based operations require node location functionality. The search implemantation traverses until matching data is found:
ListNode* locateNode(ListNode* head, ListItemType value) {
ListNode* current = head->next_node;
while (current != head) {
if (current->value == value) return current;
current = current->next_node;
}
return NULL;
}
Insertion after a specified position invovles updating four node references:
void insertAfter(ListNode* position, ListItemType value) {
assert(position);
ListNode* new_node = createListNode(value);
new_node->next_node = position->next_node;
new_node->previous_node = position;
position->next_node->previous_node = new_node;
position->next_node = new_node;
}
Node removal at a specific position updates neighboring references before deallocation:
void removeNode(ListNode* position) {
assert(position);
position->next_node->previous_node = position->previous_node;
position->previous_node->next_node = position->next_node;
free(position);
}
Memory deallocation traverses and releases all nodes including the sentinel:
void destroyList(ListNode* head) {
assert(head);
ListNode* current = head->next_node;
while (current != head) {
ListNode* next = current->next_node;
free(current);
current = next;
}
free(head);
}