实现带头结点的双向链表需要考虑节点的结构和各种操作的实现。双向链表的每个节点除了包含数据和指向下一个节点的指针外,还需要一个指向前一个节点的指针。这使得在双向链表中,可以从任意节点直接访问其前驱和后继节点,提高了某些操作的效率。
双向链表结构定义和操作
结构定义
双向链表的节点结构定义如下:
#include <stdio.h>
#include <stdlib.h>
// 双向链表节点结构
typedef struct Node {
int data; // 节点数据域
struct Node* prev; // 指向前一个节点的指针
struct Node* next; // 指向后一个节点的指针
} Node;
// 双向链表结构体定义
typedef struct {
Node* head; // 头结点指针,指向链表的头部
int size; // 链表中节点的数量
} DoublyLinkedList;
常用操作及时间复杂度分析
- 创建双向链表 (
createDoublyLinkedList
)
// 创建空的双向链表
DoublyLinkedList* createDoublyLinkedList() {
DoublyLinkedList* list = malloc(sizeof(DoublyLinkedList));
if (list == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
// 创建头结点
list->head = malloc(sizeof(Node));
if (list->head == NULL) {
fprintf(stderr, "Memory allocation failed\n");
free(list);
return NULL;
}
list->head->prev = NULL;
list->head->next = NULL;
list->size = 0;
return list;
}
- 时间复杂度: O(1)
- 创建双向链表只涉及分配内存和初始化头结点,操作时间为常数。
- 2销毁双向链表 (
destroyDoublyLinkedList
)
// 销毁双向链表
void destroyDoublyLinkedList(DoublyLinkedList* list) {
if (list == NULL) return;
Node* curr = list->head;
while (curr != NULL) {
Node* next = curr->next;
free(curr);
curr = next;
}
free(list);
}
- 时间复杂度: O(n)
- 销毁双向链表需要遍历链表的所有节点,并释放每个节点的内存。
- 3头部插入 (
insertAtHead
)
// 头部插入节点
void insertAtHead(DoublyLinkedList* list, int data) {
Node* newNode = malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
newNode->data = data;
// 将新节点插入到头结点之后
newNode->prev = list->head;
newNode->next = list->head->next;
if (list->head->next != NULL) {
list->head->next->prev = newNode;
}
list->head->next = newNode;
list->size++; // 链表大小加1
}
- 时间复杂度: O(1)
- 在双向链表的头部插入节点只需要修改几个指针的指向,操作时间为常数。
- 4尾部插入 (
insertAtTail
)
// 尾部插入节点
void insertAtTail(DoublyLinkedList* list, int data) {
Node* newNode = malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
newNode->data = data;
newNode->next = NULL;
// 找到尾节点
Node* tail = list->head;
while (tail->next != NULL) {
tail = tail->next;
}
// 将新节点插入到尾节点之后
newNode->prev = tail;
tail->next = newNode;
list->size++; // 链表大小加1
}
- 时间复杂度: O(n)
- 在双向链表的尾部插入节点需要遍历整个链表找到尾节点,因此操作时间为线性。
- 5指定位置插入 (
insertAtIndex
)
// 指定位置插入节点
void insertAtIndex(DoublyLinkedList* list, int index, int data) {
if (index < 0 || index > list->size) {
fprintf(stderr, "Invalid index\n");
return;
}
Node* newNode = malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
newNode->data = data;
// 找到要插入位置的前一个节点
Node* curr = list->head;
for (int i = 0; i < index; i++) {
curr = curr->next;
}
// 插入新节点
newNode->prev = curr;
newNode->next = curr->next;
if (curr->next != NULL) {
curr->next->prev = newNode;
}
curr->next = newNode;
list->size++; // 链表大小加1
}
- 时间复杂度: O(n)
- 在双向链表的指定位置插入节点需要先遍历找到指定位置的前一个节点,因此操作时间为线性。
- 6头部删除 (
deleteAtHead
)
// 头部删除节点
void deleteAtHead(DoublyLinkedList* list) {
if (list->head->next == NULL) {
fprintf(stderr, "List is empty\n");
return;
}
// 删除头节点后的第一个节点
Node* firstNode = list->head->next;
list->head->next = firstNode->next;
if (firstNode->next != NULL) {
firstNode->next->prev = list->head;
}
free(firstNode);
list->size--; // 链表大小减1
}
- 时间复杂度: O(1)
- 删除双向链表的头节点只需要修改几个指针的指向,并释放原头节点的内存,操作时间为常数。
- 7尾部删除 (
deleteAtTail
)
// 尾部删除节点
void deleteAtTail(DoublyLinkedList* list) {
if (list->head->next == NULL) {
fprintf(stderr, "List is empty\n");
return;
}
// 找到尾节点的前一个节点
Node* prev = list->head;
while (prev->next->next != NULL) {
prev = prev->next;
}
// 删除尾节点
Node* tail = prev->next;
prev->next = NULL;
free(tail);
list->size--; // 链表大小减1
}
- 时间复杂度: O(n)
- 删除双向链表的尾节点需要先遍历找到尾节点的前一个节点,然后修改指针,释放尾节点的内存,因此操作时间为线性。
- 8指定位置删除 (
deleteAtIndex
)
// 指定位置删除节点
void deleteAtIndex(DoublyLinkedList* list, int index) {
if (index < 0 || index >= list->size) {
fprintf(stderr, "Invalid index\n");
return;
}
// 找到要删除的节点
Node* curr = list->head;
for (int i = 0; i <= index; i++) {
curr = curr->next;
}
// 删除节点
curr->prev->next = curr->next;
if (curr->next != NULL) {
curr->next->prev = curr->prev;
}
free(curr);
list->size--; // 链表大小减1
}
- 时间复杂度: O(n)
- 删除双向链表的指定位置节点需要先遍历找到指定位置的节点,然后修改指针,释放节点的内存,因此操作时间为线性。
- 9按值查找 (
findByValue
)
// 按值查找节点
Node* findByValue(DoublyLinkedList* list, int data) {
Node* curr = list->head->next;
while (curr != NULL && curr->data != data) {
curr = curr->next;
}
return curr;
}
- 时间复杂度: O(n)
- 按值查找双向链表中的节点需要遍历整个链表,直到找到目标节点或者到达链表末尾,因此操作时间为线性。
- 10按索引查找 (
findByIndex
)
// 按索引查找节点
Node* findByIndex(DoublyLinkedList* list, int index) {
if (index < 0 || index >= list->size) {
fprintf(stderr, "Invalid index\n");
return NULL;
}
Node* curr = list->head->next;
for (int i = 0; i < index; i++) {
curr = curr->next;
}
return curr;
}
- 时间复杂度: O(n)
- 按索引查找双向链表中的节点需要遍历链表,直到找到指定索引位置的节点,因此操作时间为线性。
- 11修改节点数据 (
updateNode
)
// 修改节点数据
void updateNode(Node* node, int newData) {
if (node != NULL) {
node->data = newData;
}
}
- 时间复杂度: O(1)
- 直接修改节点的数据域,操作时间与链表长度无关,因此是常数时间复杂度。
总结
双向链表通过每个节点存储前驱和后继节点的指针,支持从任意位置直接访问前后节点,使得插入、删除等操作在某些场景下比单链表更加高效。然而,由于需要额外的指针空间和更复杂的指针操作,双向链表的空间复杂度较高,并且在插入、删除操作涉及到节点查找时,时间复杂度与链表长度成正比。
#include <stdio.h>
#include <stdlib.h>
// 双向链表节点结构
typedef struct Node {
int data; // 节点数据域
struct Node* prev; // 指向前一个节点的指针
struct Node* next; // 指向后一个节点的指针
} Node;
// 双向链表结构体定义
typedef struct {
Node* head; // 头结点指针,指向链表的头部
int size; // 链表中节点的数量
} DoublyLinkedList;
// 创建空的双向链表
DoublyLinkedList* createDoublyLinkedList() {
DoublyLinkedList* list = malloc(sizeof(DoublyLinkedList));
if (list == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
// 创建头结点
list->head = malloc(sizeof(Node));
if (list->head == NULL) {
fprintf(stderr, "Memory allocation failed\n");
free(list);
return NULL;
}
list->head->prev = NULL;
list->head->next = NULL;
list->size = 0;
return list;
}
// 销毁双向链表
void destroyDoublyLinkedList(DoublyLinkedList* list) {
if (list == NULL) return;
Node* curr = list->head;
while (curr != NULL) {
Node* next = curr->next;
free(curr);
curr = next;
}
free(list);
}
// 头部插入节点
void insertAtHead(DoublyLinkedList* list, int data) {
Node* newNode = malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
newNode->data = data;
// 将新节点插入到头结点之后
newNode->prev = list->head;
newNode->next = list->head->next;
if (list->head->next != NULL) {
list->head->next->prev = newNode;
}
list->head->next = newNode;
list->size++; // 链表大小加1
}
// 尾部插入节点
void insertAtTail(DoublyLinkedList* list, int data) {
Node* newNode = malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
newNode->data = data;
newNode->next = NULL;
// 找到尾节点
Node* tail = list->head;
while (tail->next != NULL) {
tail = tail->next;
}
// 将新节点插入到尾节点之后
newNode->prev = tail;
tail->next = newNode;
list->size++; // 链表大小加1
}
// 指定位置插入节点
void insertAtIndex(DoublyLinkedList* list, int index, int data) {
if (index < 0 || index > list->size) {
fprintf(stderr, "Invalid index\n");
return;
}
Node* newNode = malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
newNode->data = data;
// 找到要插入位置的前一个节点
Node* curr = list->head;
for (int i = 0; i < index; i++) {
curr = curr->next;
}
// 插入新节点
newNode->prev = curr;
newNode->next = curr->next;
if (curr->next != NULL) {
curr->next->prev = newNode;
}
curr->next = newNode;
list->size++; // 链表大小加1
}
// 头部删除节点
void deleteAtHead(DoublyLinkedList* list) {
if (list->head->next == NULL) {
fprintf(stderr, "List is empty\n");
return;
}
// 删除头节点后的第一个节点
Node* firstNode = list->head->next;
list->head->next = firstNode->next;
if (firstNode->next != NULL) {
firstNode->next->prev = list->head;
}
free(firstNode);
list->size--; // 链表大小减1
}
// 尾部删除节点
void deleteAtTail(DoublyLinkedList* list) {
if (list->head->next == NULL) {
fprintf(stderr, "List is empty\n");
return;
}
// 找到尾节点的前一个节点
Node* prev = list->head;
while (prev->next->next != NULL) {
prev = prev->next;
}
// 删除尾节点
Node* tail = prev->next;
prev->next = NULL;
free(tail);
list->size--; // 链表大小减1
}
// 指定位置删除节点
void deleteAtIndex(DoublyLinkedList* list, int index) {
if (index < 0 || index >= list->size) {
fprintf(stderr, "Invalid index\n");
return;
}
// 找到要删除的节点
Node* curr = list->head;
for (int i = 0; i <= index; i++) {
curr = curr->next;
}
// 删除节点
curr->prev->next = curr->next;
if (curr->next != NULL) {
curr->next->prev = curr->prev;
}
free(curr);
list->size--; // 链表大小减1
}
// 按值查找节点
Node* findByValue(DoublyLinkedList* list, int data) {
Node* curr = list->head->next;
while (curr != NULL && curr->data != data) {
curr = curr->next;
}
return curr;
}
// 按索引查找节点
Node* findByIndex(DoublyLinkedList* list, int index) {
if (index < 0 || index >= list->size) {
fprintf(stderr, "Invalid index\n");
return NULL;
}
Node* curr = list->head->next;
for (int i = 0; i < index; i++) {
curr = curr->next;
}
return curr;
}
// 修改节点数据
void updateNode(Node* node, int newData) {
if (node != NULL) {
node->data = newData;
}
}
// 打印双向链表
void printDoublyLinkedList(DoublyLinkedList* list) {
Node* curr = list->head->next;
printf("Doubly Linked List: ");
while (curr != NULL) {
printf("%d -> ", curr->data);
curr = curr->next;
}
printf("NULL\n");
}
int main() {
DoublyLinkedList* list = createDoublyLinkedList();
// 插入节点示例
insertAtHead(list, 1);
insertAtTail(list, 2);
insertAtIndex(list, 1, 3);
// 打印链表
printDoublyLinkedList(list);
// 删除节点示例
deleteAtHead(list);
deleteAtIndex(list, 1);
// 打印链表
printDoublyLinkedList(list);
// 按值查找节点示例
insertAtHead(list, 4);
Node* node = findByValue(list, 3);
if (node != NULL) {
printf("Node found with value 3\n");
} else {
printf("Node with value 3 not found\n");
}
// 修改节点数据示例
node = findByValue(list, 4);
if (node != NULL) {
updateNode(node, 5);
printf("Node value updated to 5\n");
}
// 打印链表
printDoublyLinkedList(list);
// 销毁链表
destroyDoublyLinkedList(list);
return 0;
}