跳到主要内容
数据结构:单向链表与双向链表核心操作及实现 | 极客日志
C 算法
数据结构:单向链表与双向链表核心操作及实现 综述由AI生成 单向链表和双向链表的数据结构定义及基础操作。内容涵盖链表销毁、查找中间节点、查找倒数第 k 个节点、链表倒置与归并排序。重点阐述了使用快慢指针法进行环检测、计算环长度及获取环入口位置的算法原理与实现。同时展示了双向链表的头插尾插、遍历及删除操作,并对比了两种链表的时间复杂度。最后提供了内存管理与调试的最佳实践建议。
暗影行者 发布于 2026/3/28 更新于 2026/5/23 29 浏览1. 单向链表的基础操作
1.1 数据结构定义
typedef struct ListNode {
int val;
struct ListNode * next ;
} ListNode;
1.2 单向链表的销毁
链表的销毁是内存管理的关键环节,必须确保每个节点都被正确释放。
void destroyLinkedList (ListNode** head) {
if (head == NULL || *head == NULL ) {
return ;
}
ListNode* current = *head;
ListNode* next = NULL ;
while (current != NULL ) {
next = current->next;
free (current);
current = next;
}
*head = NULL ;
}
void testDestroy () {
ListNode* head = createNode(1 );
head->next = createNode(2 );
head->next->next = createNode(3 );
printf ("销毁前链表长度:%d\n" , getLength(head));
destroyLinkedList(&head);
if (head == NULL ) {
printf ("链表已成功销毁\n" );
}
}
2. 单向链表的关键算法
2.1 查找中间节点 查找链表的中间节点是面试中的常见问题,使用快慢指针法可以高效解决。
ListNode* findMiddleNode (ListNode* head) {
if (head == NULL || head->next == NULL ) {
return head;
}
ListNode* slow = head;
ListNode* fast = head;
while (fast != NULL && fast->next != NULL ) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
void testFindMiddle () {
ListNode* head = createListFromArray(1 , 2 , 3 , 4 , 5 );
ListNode* middle = findMiddleNode(head);
printf ("中间节点值:%d\n" , middle->val);
ListNode* head2 = createListFromArray(1 , 2 , 3 , 4 );
ListNode* middle2 = findMiddleNode(head2);
printf ("中间节点值:%d\n" , middle2->val);
}
2.2 查找倒数第 k 个节点
ListNode* findKthFromEnd (ListNode* head, int k) {
if (head == NULL || k <= 0 ) {
return NULL ;
}
ListNode* fast = head;
ListNode* slow = head;
for (int i = 0 ; i < k; i++) {
if (fast == NULL ) {
return NULL ;
}
fast = fast->next;
}
while (fast != NULL ) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
void testFindKthFromEnd () {
ListNode* head = createListFromArray(1 , 2 , 3 , 4 , 5 );
ListNode* node1 = findKthFromEnd(head, 1 );
printf ("倒数第 1 个节点:%d\n" , node1->val);
ListNode* node3 = findKthFromEnd(head, 3 );
printf ("倒数第 3 个节点:%d\n" , node3->val);
ListNode* node5 = findKthFromEnd(head, 5 );
printf ("倒数第 5 个节点:%d\n" , node5->val);
}
2.3 链表倒置
ListNode* reverseList (ListNode* head) {
ListNode* prev = NULL ;
ListNode* current = head;
ListNode* next = NULL ;
while (current != NULL ) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
return prev;
}
ListNode* reverseListRecursive (ListNode* head) {
if (head == NULL || head->next == NULL ) {
return head;
}
ListNode* newHead = reverseListRecursive(head->next);
head->next->next = head;
head->next = NULL ;
return newHead;
}
void testReverseList () {
ListNode* head = createListFromArray(1 , 2 , 3 , 4 , 5 );
printf ("原始链表:" );
printList(head);
ListNode* reversed = reverseList(head);
printf ("倒置后链表:" );
printList(reversed);
ListNode* restored = reverseList(reversed);
printf ("再次倒置后:" );
printList(restored);
}
2.4 链表排序 链表排序相比数组排序更为复杂,这里介绍归并排序的实现。
ListNode* sortList (ListNode* head) {
if (head == NULL || head->next == NULL ) {
return head;
}
ListNode* slow = head;
ListNode* fast = head;
ListNode* prev = NULL ;
while (fast != NULL && fast->next != NULL ) {
prev = slow;
slow = slow->next;
fast = fast->next->next;
}
if (prev != NULL ) {
prev->next = NULL ;
}
ListNode* left = sortList(head);
ListNode* right = sortList(slow);
return mergeSortedLists(left, right);
}
ListNode* mergeSortedLists (ListNode* l1, ListNode* l2) {
ListNode dummy;
ListNode* tail = &dummy;
while (l1 != NULL && l2 != NULL ) {
if (l1->val <= l2->val) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
if (l1 != NULL ) {
tail->next = l1;
} else {
tail->next = l2;
}
return dummy.next;
}
void testSortList () {
ListNode* head = createListFromArray(5 , 2 , 8 , 1 , 9 , 3 );
printf ("排序前:" );
printList(head);
ListNode* sorted = sortList(head);
printf ("排序后:" );
printList(sorted);
}
3. 单向链表的环检测与处理
3.1 判断链表是否有环 使用快慢指针法(Floyd 判圈算法)可以高效检测链表是否有环。
ListNode* hasCycle (ListNode* head) {
if (head == NULL || head->next == NULL ) {
return NULL ;
}
ListNode* slow = head;
ListNode* fast = head;
while (fast != NULL && fast->next != NULL ) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
return slow;
}
}
return NULL ;
}
void testCycleDetection () {
ListNode* list1 = createListFromArray(1 , 2 , 3 , 4 , 5 );
printf ("链表 1 是否有环:%s\n" , hasCycle(list1) ? "有环" : "无环" );
ListNode* list2 = createListFromArray(1 , 2 , 3 , 4 , 5 );
ListNode* node3 = list2->next->next;
ListNode* tail = list2;
while (tail->next != NULL ) {
tail = tail->next;
}
tail->next = node3;
printf ("链表 2 是否有环:%s\n" , hasCycle(list2) ? "有环" : "无环" );
tail->next = NULL ;
destroyLinkedList(&list2);
}
3.2 获取环的长度
int getCycleLength (ListNode* meetingPoint) {
if (meetingPoint == NULL ) {
return 0 ;
}
ListNode* current = meetingPoint->next;
int length = 1 ;
while (current != meetingPoint) {
current = current->next;
length++;
}
return length;
}
void testGetCycleLength () {
ListNode* head = createListFromArray(1 , 2 , 3 , 4 , 5 );
ListNode* node3 = head->next->next;
ListNode* tail = head;
while (tail->next != NULL ) {
tail = tail->next;
}
tail->next = node3;
ListNode* meetingPoint = hasCycle(head);
if (meetingPoint != NULL ) {
int cycleLength = getCycleLength(meetingPoint);
printf ("环的长度为:%d\n" , cycleLength);
}
tail->next = NULL ;
destroyLinkedList(&head);
}
3.3 获取环的入口位置
ListNode* detectCycleEntry (ListNode* head) {
ListNode* meetingPoint = hasCycle(head);
if (meetingPoint == NULL ) {
return NULL ;
}
ListNode* ptr1 = head;
ListNode* ptr2 = meetingPoint;
while (ptr1 != ptr2) {
ptr1 = ptr1->next;
ptr2 = ptr2->next;
}
return ptr1;
}
void testDetectCycleEntry () {
ListNode* head = createListFromArray(1 , 2 , 3 , 4 , 5 );
ListNode* node3 = head->next->next;
ListNode* tail = head;
while (tail->next != NULL ) {
tail = tail->next;
}
tail->next = node3;
ListNode* entry = detectCycleEntry(head);
if (entry != NULL ) {
printf ("环入口节点的值为:%d\n" , entry->val);
}
tail->next = NULL ;
destroyLinkedList(&head);
}
4. 双向链表的实现 双向链表相比单向链表,每个节点多了一个指向前驱节点的指针,这使得双向链表在插入、删除操作上更加高效。
4.1 数据结构定义
typedef struct DListNode {
int val;
struct DListNode * prev ;
struct DListNode * next ;
} DListNode;
4.2 双向链表的创建和销毁
DListNode* createDListNode (int val) {
DListNode* node = (DListNode*)malloc (sizeof (DListNode));
if (node == NULL ) {
printf ("内存分配失败\n" );
return NULL ;
}
node->val = val;
node->prev = NULL ;
node->next = NULL ;
return node;
}
void destroyDLinkedList (DListNode** head) {
if (head == NULL || *head == NULL ) {
return ;
}
DListNode* current = *head;
DListNode* next = NULL ;
while (current != NULL ) {
next = current->next;
free (current);
current = next;
}
*head = NULL ;
printf ("双向链表已销毁\n" );
}
DListNode* createDListFromArray (int * arr, int size) {
if (arr == NULL || size <= 0 ) {
return NULL ;
}
DListNode* head = createDListNode(arr[0 ]);
DListNode* tail = head;
for (int i = 1 ; i < size; i++) {
DListNode* newNode = createDListNode(arr[i]);
tail->next = newNode;
newNode->prev = tail;
tail = newNode;
}
return head;
}
4.3 双向链表的头插和尾插
void insertAtHead (DListNode** head, int val) {
DListNode* newNode = createDListNode(val);
if (*head == NULL ) {
*head = newNode;
return ;
}
newNode->next = *head;
(*head)->prev = newNode;
*head = newNode;
}
void insertAtTail (DListNode** head, int val) {
DListNode* newNode = createDListNode(val);
if (*head == NULL ) {
*head = newNode;
return ;
}
DListNode* tail = *head;
while (tail->next != NULL ) {
tail = tail->next;
}
tail->next = newNode;
newNode->prev = tail;
}
void testInsertOperations () {
DListNode* head = NULL ;
insertAtTail(&head, 1 );
insertAtTail(&head, 2 );
insertAtTail(&head, 3 );
printf ("尾插后链表:" );
printDListForward(head);
insertAtHead(&head, 0 );
printf ("头插 0 后链表:" );
printDListForward(head);
}
4.4 双向链表的遍历
void printDListForward (DListNode* head) {
DListNode* current = head;
printf ("正向遍历:" );
while (current != NULL ) {
printf ("%d" , current->val);
if (current->next != NULL ) {
printf (" <-> " );
}
current = current->next;
}
printf (" -> NULL\n" );
}
void printDListBackward (DListNode* head) {
if (head == NULL ) {
printf ("链表为空\n" );
return ;
}
DListNode* tail = head;
while (tail->next != NULL ) {
tail = tail->next;
}
printf ("反向遍历:NULL" );
while (tail != NULL ) {
printf (" <- %d" , tail->val);
tail = tail->prev;
}
printf ("\n" );
}
void testDListTraversal () {
int arr[] = {1 , 2 , 3 , 4 , 5 };
DListNode* head = createDListFromArray(arr, 5 );
printDListForward(head);
printDListBackward(head);
destroyDLinkedList(&head);
}
4.5 双向链表的删除操作 双向链表的删除操作比单向链表更高效,因为可以直接通过前驱指针找到前一个节点。
int deleteNode (DListNode** head, int val) {
if (*head == NULL ) {
return 0 ;
}
DListNode* current = *head;
while (current != NULL ) {
if (current->val == val) {
if (current->prev != NULL ) {
current->prev->next = current->next;
} else {
*head = current->next;
}
if (current->next != NULL ) {
current->next->prev = current->prev;
}
free (current);
return 1 ;
}
current = current->next;
}
return 0 ;
}
int deleteNodeAtPosition (DListNode** head, int position) {
if (*head == NULL || position < 0 ) {
return 0 ;
}
DListNode* current = *head;
int index = 0 ;
while (current != NULL && index < position) {
current = current->next;
index++;
}
if (current == NULL ) {
return 0 ;
}
if (current->prev != NULL ) {
current->prev->next = current->next;
} else {
*head = current->next;
}
if (current->next != NULL ) {
current->next->prev = current->prev;
}
free (current);
return 1 ;
}
void testDeleteOperations () {
int arr[] = {1 , 2 , 3 , 4 , 5 };
DListNode* head = createDListFromArray(arr, 5 );
printf ("原始链表:" );
printDListForward(head);
deleteNode(&head, 3 );
printf ("删除 3 后:" );
printDListForward(head);
deleteNodeAtPosition(&head, 1 );
printf ("删除位置 1 后:" );
printDListForward(head);
destroyDLinkedList(&head);
}
4.6 双向链表的查找与替换
5. 链表操作的时间复杂度对比 操作 单向链表 双向链表 备注 访问节点 O(n) O(n) 都需要遍历 插入头节点 O(1) O(1) 两者都高效 插入尾节点 O(n) O(1) 双向链表维护尾指针时更优 删除指定节点 O(n) O(1) 双向链表直接定位更高效 查找中间节点 O(n) O(n) 都需要快慢指针法 倒置链表 O(n) O(n) 时间复杂度相同 判断是否有环 O(n) O(n) 都需要快慢指针法
6. 实用技巧和最佳实践
6.1 内存管理注意事项
每次 malloc 后检查返回值 :确保内存分配成功
每个 malloc 必须有对应的 free :防止内存泄漏
free 后立即将指针置为 NULL :防止野指针
有环链表需要先断开环再销毁 :避免无限循环
6.2 调试技巧
打印链表内容 :在关键步骤打印链表状态
使用断言 :检查链表指针的有效性
内存检测工具 :使用 valgrind 等工具检测内存问题
7. 总结
单向链表 :包括创建、销毁、查找中间节点、查找倒数第 k 个节点、倒置、排序等基本操作
环检测与处理 :使用快慢指针法检测环、计算环长、找到环入口
双向链表 :完整的创建、销毁、插入、遍历、删除操作实现
链表作为基础数据结构,在算法面试和实际开发中都有广泛应用。掌握这些核心操作不仅有助于解决算法问题,也能提高对内存管理和指针操作的理解。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online