链表
C 语言中的链表节点定义
在 C 语言中,我们通常使用结构体(struct
)来定义链表的节点。
1 2 3 4 5 6 7 8 9 10 11
| #include <stdio.h> #include <stdlib.h>
typedef struct Node { int data; struct Node *next; } Node;
|
链表的基本操作
1 创建新节点
创建新节点是链表操作的基础。我们需要使用 malloc
为新节点分配内存,并初始化其数据和指针。
1 2 3 4 5 6 7 8 9 10 11
| Node* createNode(int value) { Node* newNode = (Node*)malloc(sizeof(Node)); if (newNode == NULL) { printf("内存分配失败!\n"); exit(1); } newNode->data = value; newNode->next = NULL; return newNode; }
|
2 链表的初始化(创建空链表)
空链表通常用一个 NULL
指针表示。
1 2 3 4
| Node* initLinkedList() { return NULL; }
|
3 在链表头部插入节点
这是最简单的插入操作。
1 2 3 4 5 6
| Node* insertAtHead(Node* head, int value) { Node* newNode = createNode(value); newNode->next = head; return newNode; }
|
4 在链表尾部插入节点
需要遍历链表找到尾节点,然后将其 next
指针指向新节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Node* insertAtTail(Node* head, int value) { Node* newNode = createNode(value); if (head == NULL) { return newNode; }
Node* current = head; while (current->next != NULL) { current = current->next; } current->next = newNode; return head; }
|
5 遍历链表并打印数据
这是最常用的操作之一,用于查看链表中的所有元素。
1 2 3 4 5 6 7 8 9 10
| void printLinkedList(Node* head) { Node* current = head; printf("链表内容:"); while (current != NULL) { printf("%d -> ", current->data); current = current->next; } printf("NULL\n"); }
|
6 查找节点
根据数据查找特定节点。
1 2 3 4 5 6 7 8 9 10 11
| Node* findNode(Node* head, int value) { Node* current = head; while (current != NULL) { if (current->data == value) { return current; } current = current->next; } return NULL; }
|
7 删除节点
删除指定值的节点。这里我们处理了三种情况:删除头节点、删除中间节点、删除不存在的节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Node* deleteNode(Node* head, int value) { if (head == NULL) { return NULL; }
if (head->data == value) { Node* temp = head; head = head->next; free(temp); return head; }
Node* current = head; while (current->next != NULL && current->next->data != value) { current = current->next; }
if (current->next != NULL) { Node* temp = current->next; current->next = temp->next; free(temp); } else { printf("未找到值为 %d 的节点进行删除。\n", value); } return head; }
|
8 释放链表内存
完成链表操作后,务必释放所有节点的内存,避免内存泄漏。
1 2 3 4 5 6 7 8 9 10
| void freeLinkedList(Node* head) { Node* current = head; while (current != NULL) { Node* temp = current; current = current->next; free(temp); } printf("链表内存已释放。\n"); }
|
完整示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
| #include <stdio.h> #include <stdlib.h>
typedef struct Node { int data; struct Node *next; } Node;
Node* createNode(int value) { Node* newNode = (Node*)malloc(sizeof(Node)); if (newNode == NULL) { printf("内存分配失败!\n"); exit(1); } newNode->data = value; newNode->next = NULL; return newNode; }
Node* insertAtHead(Node* head, int value) { Node* newNode = createNode(value); newNode->next = head; return newNode; }
Node* insertAtTail(Node* head, int value) { Node* newNode = createNode(value); if (head == NULL) { return newNode; } Node* current = head; while (current->next != NULL) { current = current->next; } current->next = newNode; return head; }
void printLinkedList(Node* head) { Node* current = head; printf("链表内容:"); while (current != NULL) { printf("%d -> ", current->data); current = current->next; } printf("NULL\n"); }
Node* findNode(Node* head, int value) { Node* current = head; while (current != NULL) { if (current->data == value) { return current; } current = current->next; } return NULL; }
Node* deleteNode(Node* head, int value) { if (head == NULL) { printf("链表为空,无法删除。\n"); return NULL; }
if (head->data == value) { Node* temp = head; head = head->next; free(temp); printf("成功删除头节点 %d。\n", value); return head; }
Node* current = head; while (current->next != NULL && current->next->data != value) { current = current->next; }
if (current->next != NULL) { Node* temp = current->next; current->next = temp->next; free(temp); printf("成功删除节点 %d。\n", value); } else { printf("未找到值为 %d 的节点进行删除。\n", value); } return head; }
void freeLinkedList(Node* head) { Node* current = head; while (current != NULL) { Node* temp = current; current = current->next; free(temp); } printf("链表内存已释放。\n"); }
int main() { Node* head = NULL;
printf("------ 插入操作 ------\n"); head = insertAtHead(head, 10); printLinkedList(head); head = insertAtTail(head, 20); printLinkedList(head); head = insertAtHead(head, 5); printLinkedList(head);
head = insertAtTail(head, 30); printLinkedList(head);
printf("\n------ 查找操作 ------\n"); if (findNode(head, 10) != NULL) { printf("找到了值为 10 的节点。\n"); } else { printf("未找到值为 10 的节点。\n"); }
if (findNode(head, 100) != NULL) { printf("找到了值为 100 的节点。\n"); } else { printf("未找到值为 100 的节点。\n"); }
printf("\n------ 删除操作 ------\n"); head = deleteNode(head, 5); printLinkedList(head); head = deleteNode(head, 20); printLinkedList(head);
head = deleteNode(head, 100); printLinkedList(head);
head = deleteNode(head, 30); printLinkedList(head); head = deleteNode(head, 10); printLinkedList(head);
printf("\n------ 内存释放 ------\n"); head = insertAtTail(head, 1); head = insertAtTail(head, 2); printLinkedList(head); freeLinkedList(head); head = NULL; printLinkedList(head);
return 0; }
|
链表的变种
除了上面介绍的单向链表,还有其他几种常见的链表类型:
- 双向链表(Doubly Linked List): 每个节点除了指向下一个节点的指针
next
外,还包含一个指向前一个节点的指针 prev
。这使得链表可以双向遍历,但需要额外的空间存储 prev
指针。
- 循环链表(Circular Linked List): 链表的最后一个节点的
next
指针不是 NULL
,而是指向头节点,形成一个环。
双向链表
什么是双向链表?
双向链表,顾名思义,就是每个节点不仅知道“下一个”是谁,也知道“上一个”是谁。它在单向链表的基础上,为每个节点额外增加了一个指向前一个节点的指针。
双向链表节点的结构:
1 2 3 4 5 6 7 8 9
| #include <stdio.h> #include <stdlib.h>
typedef struct DoublyNode { int data; struct DoublyNode *prev; struct DoublyNode *next; } DoublyNode;
|
特点:
- 双向遍历: 可以从头到尾遍历,也可以从尾到头遍历。
- 插入和删除更灵活: 在已知待操作节点的情况下,其前驱和后继节点都可以直接访问,从而简化了插入和删除操作(特别是删除指定节点)。
- 空间开销增加: 每个节点需要额外存储一个
prev
指针,因此比单向链表占用更多内存。
双向链表的基本操作
我们将重点展示与单向链表不同的或更高效的操作。
创建新节点
1 2 3 4 5 6 7 8 9 10 11 12
| DoublyNode* createDoublyNode(int value) { DoublyNode* newNode = (DoublyNode*)malloc(sizeof(DoublyNode)); if (newNode == NULL) { printf("内存分配失败!\n"); exit(1); } newNode->data = value; newNode->prev = NULL; newNode->next = NULL; return newNode; }
|
在链表头部插入节点
1 2 3 4 5 6 7 8 9 10
| DoublyNode* insertDoublyAtHead(DoublyNode* head, int value) { DoublyNode* newNode = createDoublyNode(value); if (head == NULL) { return newNode; } newNode->next = head; head->prev = newNode; return newNode; }
|
在链表尾部插入节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| DoublyNode* insertDoublyAtTail(DoublyNode* head, int value) { DoublyNode* newNode = createDoublyNode(value); if (head == NULL) { return newNode; } DoublyNode* current = head; while (current->next != NULL) { current = current->next; } current->next = newNode; newNode->prev = current; return head; }
|
在指定节点后插入
这是双向链表的一个便利之处,单向链表需要找到前驱节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void insertAfterNode(DoublyNode* node, int value) { if (node == NULL) { printf("无法在空节点后插入。\n"); return; } DoublyNode* newNode = createDoublyNode(value); newNode->next = node->next; newNode->prev = node;
if (node->next != NULL) { node->next->prev = newNode; } node->next = newNode; }
|
删除指定节点(已知节点指针)
这也是双向链表删除操作的优势。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
DoublyNode* deleteGivenDoublyNode(DoublyNode* head, DoublyNode* nodeToDelete) { if (head == NULL || nodeToDelete == NULL) { return head; }
if (nodeToDelete == head) { head = nodeToDelete->next; if (head != NULL) { head->prev = NULL; } } else { nodeToDelete->prev->next = nodeToDelete->next; if (nodeToDelete->next != NULL) { nodeToDelete->next->prev = nodeToDelete->prev; } } free(nodeToDelete); return head; }
|
遍历双向链表(正向和反向)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void printDoublyLinkedListForward(DoublyNode* head) { DoublyNode* current = head; printf("双向链表(正向):"); while (current != NULL) { printf("%d <-> ", current->data); current = current->next; } printf("NULL\n"); }
void printDoublyLinkedListBackward(DoublyNode* head) { if (head == NULL) { printf("双向链表(反向):NULL\n"); return; } DoublyNode* current = head; while (current->next != NULL) { current = current->next; } printf("双向链表(反向):"); while (current != NULL) { printf("%d <-> ", current->data); current = current->prev; } printf("NULL\n"); }
|
释放双向链表内存
1 2 3 4 5 6 7 8 9 10
| void freeDoublyLinkedList(DoublyNode* head) { DoublyNode* current = head; while (current != NULL) { DoublyNode* temp = current; current = current->next; free(temp); } printf("双向链表内存已释放。\n"); }
|
循环链表(Circular Linked List)
什么是循环链表?
循环链表是一种特殊的单向链表(也可以是双向的)。它的最后一个节点的 next
指针不是指向 NULL
,而是指向链表的头节点,形成一个环。
循环链表节点的结构(与单向链表相同):
1 2 3 4 5
| typedef struct CircularNode { int data; struct CircularNode *next; } CircularNode;
|
特点:
- 无“尾”概念: 链表中没有真正的“尾节点”(其
next
指向 NULL
的节点),遍历可以无限进行下去,直到回到起点。
- 从任何节点开始遍历: 从链表中的任何一个节点开始,都可以遍历到所有节点。
- 插入删除无需特殊处理头尾: 插入和删除操作相对单向链表在处理头尾时更统一。
- 遍历需要停止条件: 需要额外的逻辑来判断是否已经遍历了一圈,避免无限循环。
2 循环链表的基本操作
由于循环链表的结构与单向链表相似,许多操作逻辑是类似的,但需要注意“环”的特性。为了简化,我们通常会维护一个指向尾节点的指针,而不是头节点。因为通过尾节点,可以 O(1) 时间访问到头节点(tail->next
就是头节点)。
创建新节点(与单向链表相同)
1 2 3 4 5 6 7 8 9 10 11
| CircularNode* createCircularNode(int value) { CircularNode* newNode = (CircularNode*)malloc(sizeof(CircularNode)); if (newNode == NULL) { printf("内存分配失败!\n"); exit(1); } newNode->data = value; newNode->next = NULL; return newNode; }
|
循环链表的初始化(返回尾指针)
1 2 3 4
| CircularNode* initCircularLinkedList() { return NULL; }
|
在链表头部插入节点
1 2 3 4 5 6 7 8 9 10 11 12
| CircularNode* insertCircularAtHead(CircularNode* tail, int value) { CircularNode* newNode = createCircularNode(value); if (tail == NULL) { newNode->next = newNode; return newNode; } else { newNode->next = tail->next; tail->next = newNode; return tail; } }
|
在链表尾部插入节点
1 2 3 4 5 6 7 8 9 10 11
| CircularNode* insertCircularAtTail(CircularNode* tail, int value) { CircularNode* newNode = createCircularNode(value); if (tail == NULL) { newNode->next = newNode; } else { newNode->next = tail->next; tail->next = newNode; } return newNode; }
|
遍历循环链表并打印数据
需要一个额外的条件来判断是否已经回到起点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void printCircularLinkedList(CircularNode* tail) { if (tail == NULL) { printf("循环链表内容:NULL\n"); return; } CircularNode* head = tail->next; CircularNode* current = head; printf("循环链表内容:"); do { printf("%d -> ", current->data); current = current->next; } while (current != head); printf("... (循环回到 %d)\n", head->data); }
|
删除节点(已知尾节点和待删除值)
删除循环链表节点需要更细致的判断,特别是当只剩一个节点或删除头/尾节点时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| CircularNode* deleteCircularNode(CircularNode* tail, int value) { if (tail == NULL) { printf("循环链表为空,无法删除。\n"); return NULL; }
CircularNode* head = tail->next;
if (head == tail && head->data == value) { free(head); printf("成功删除唯一节点 %d。\n", value); return NULL; }
if (head->data == value) { tail->next = head->next; free(head); printf("成功删除头节点 %d。\n", value); return tail; }
CircularNode* current = head; while (current->next != head && current->next->data != value) { current = current->next; }
if (current->next->data == value) { CircularNode* temp = current->next; current->next = temp->next; if (temp == tail) { tail = current; } free(temp); printf("成功删除节点 %d。\n", value); } else { printf("未找到值为 %d 的节点进行删除。\n", value); } return tail; }
|
释放循环链表内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void freeCircularLinkedList(CircularNode* tail) { if (tail == NULL) { printf("循环链表已空,无需释放。\n"); return; } CircularNode* head = tail->next; CircularNode* current = head; do { CircularNode* temp = current; current = current->next; free(temp); } while (current != head); printf("循环链表内存已释放。\n"); }
|