FEATURED · 精选文章

链表详解:定义、作用、应用场景及与数组的区别(C/C++ 实战)

发布时间 / 2026/8/29 12:40:20
来源 / 创域科博编辑部
栏目 / 资讯中心
链表详解:定义、作用、应用场景及与数组的区别(C/C++ 实战) 1. 什么是链表链表是一种物理存储单元上非连续、非顺序的线性数据结构它由一系列节点Node组成。每个节点包含两部分一部分是存储数据元素的数据域data另一部分是存储下一个节点地址的指针域next。与数组不同链表中的各个节点在内存中并不需要连续存放节点之间通过指针相互连接形成一个链式结构。链表的第一个节点称为头节点head最后一个节点的指针域指向空NULL表示链表的结束。链表的核心思想是用指针把分散在内存各处的节点串联起来从而在逻辑上保持线性顺序。这种设计使得链表在插入和删除操作上具有天然的优势因为只需要修改指针的指向而不需要移动大量数据。2. 链表的作用链表在程序设计中主要解决以下几类问题动态内存管理链表可以在运行时按需分配和释放节点内存不需要预先知道数据总量适合数据规模不确定的场景。高效的插入与删除在已知位置插入或删除节点时链表只需要修改指针时间复杂度为 O(1)而数组需要移动大量元素时间复杂度为 O(n)。内存碎片利用链表不要求连续内存空间可以充分利用内存中的零散空闲区域。实现复杂数据结构的基础链表是栈、队列、哈希表链地址法、图的邻接表等许多高级数据结构的基础构件。3. 链表与数组的区别数组和链表是两种最基础的线性数据结构它们在内存布局、操作效率和适用场景上有显著差异。下面通过一个表格进行对比对比维度数组链表内存布局连续内存空间非连续节点分散存储内存分配静态分配或动态一次性分配动态按需分配逐个创建节点访问方式支持随机访问通过下标 O(1) 定位只能顺序访问需从头遍历 O(n)插入/删除需要移动大量元素O(n)只需修改指针O(1)已知位置时空间开销无额外指针开销但可能浪费预分配空间每个节点额外存储指针空间开销较大缓存友好性连续存储CPU 缓存命中率高节点分散缓存命中率低扩容需要重新分配更大的内存并复制数据直接新增节点即可无需整体搬迁简单来说数组擅长随机访问和缓存友好但插入删除代价高链表擅长频繁插入删除但随机访问能力弱。选择哪种结构取决于具体业务场景对操作类型的侧重。4. 链表的常见应用场景链表在真实项目和系统中有广泛的应用主要包括操作系统内存管理空闲内存块通常用链表组织便于分配和回收。文件系统文件分配表FAT和部分日志结构文件系统使用链表思想管理存储块。哈希表冲突解决链地址法用链表存储哈希冲突的元素。图的邻接表每个顶点的邻接边用链表存储节省空间。LRU 缓存淘汰哈希表 双向链表实现 O(1) 的访问和淘汰。多项式运算稀疏多项式用链表存储非零项节省存储空间。大数运算超出基本类型范围的大整数用链表按位存储。栈和队列的实现链表可实现动态扩容的栈和队列。5. C 语言实现单链表下面用 C 语言实现一个完整的单链表包含创建、插入、删除、遍历和销毁等基本操作。#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); return NULL; } 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; } // 删除指定值的第一个节点 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); // 释放内存 } return head; } // 遍历打印链表 void printList(Node *head) { Node *current head; while (current ! NULL) { printf(%d - , current-data); current current-next; } printf(NULL\n); } // 释放整个链表 void freeList(Node *head) { Node *current head; while (current ! NULL) { Node *temp current; current current-next; free(temp); } } int main() { Node *head NULL; // 头插法构建链表3 - 2 - 1 - NULL head insertAtHead(head, 1); head insertAtHead(head, 2); head insertAtHead(head, 3); printf(头插法结果: ); printList(head); // 尾插法追加节点3 - 2 - 1 - 4 - 5 - NULL head insertAtTail(head, 4); head insertAtTail(head, 5); printf(尾插法结果: ); printList(head); // 删除值为 2 的节点 head deleteNode(head, 2); printf(删除节点 2 后: ); printList(head); // 释放链表内存 freeList(head); return 0; }运行结果如下头插法结果: 3 - 2 - 1 - NULL 尾插法结果: 3 - 2 - 1 - 4 - 5 - NULL 删除节点 2 后: 3 - 1 - 4 - 5 - NULL这段代码演示了链表最核心的操作头插法利用新节点指向原头节点实现 O(1) 插入尾插法需要遍历到末尾再连接删除节点时先找到目标节点的前驱再修改指针并释放内存。注意每次 malloc 分配节点后使用完毕必须 free 释放否则会造成内存泄漏。6. C 实现双向链表C 中除了可以用 struct 手动实现链表标准库还提供了std::list双向链表和std::forward_list单向链表。下面先手动实现一个双向链表再展示标准库的用法。#include iostream using namespace std; // 双向链表节点 struct DNode { int data; DNode *prev; // 指向前驱 DNode *next; // 指向后继 DNode(int val) : data(val), prev(nullptr), next(nullptr) {} }; // 双向链表类 class DoublyLinkedList { private: DNode *head; DNode *tail; public: DoublyLinkedList() : head(nullptr), tail(nullptr) {} // 在尾部插入 void pushBack(int value) { DNode *newNode new DNode(value); if (tail nullptr) { head tail newNode; // 空链表 } else { tail-next newNode; newNode-prev tail; tail newNode; } } // 在头部插入 void pushFront(int value) { DNode *newNode new DNode(value); if (head nullptr) { head tail newNode; } else { newNode-next head; head-prev newNode; head newNode; } } // 删除指定值的第一个节点 void remove(int value) { DNode *current head; while (current ! nullptr) { if (current-data value) { if (current-prev ! nullptr) { current-prev-next current-next; } else { head current-next; // 删除的是头节点 } if (current-next ! nullptr) { current-next-prev current-prev; } else { tail current-prev; // 删除的是尾节点 } delete current; return; } current current-next; } } // 正向遍历 void printForward() { DNode *current head; while (current ! nullptr) { cout current-data - ; current current-next; } cout NULL endl; } // 反向遍历 void printBackward() { DNode *current tail; while (current ! nullptr) { cout current-data - ; current current-prev; } cout NULL endl; } // 析构函数释放所有节点 ~DoublyLinkedList() { DNode *current head; while (current ! nullptr) { DNode *temp current; current current-next; delete temp; } } }; int main() { DoublyLinkedList list; list.pushBack(10); list.pushBack(20); list.pushBack(30); list.pushFront(5); cout 正向遍历: ; list.printForward(); cout 反向遍历: ; list.printBackward(); list.remove(20); cout 删除 20 后正向遍历: ; list.printForward(); return 0; }双向链表每个节点多了一个指向前驱的指针因此可以双向遍历删除节点时不需要像单链表那样寻找前驱节点代码更简洁。代价是每个节点多占用一个指针的内存空间。7. C 标准库链表用法在实际开发中除非有特殊需求通常直接使用 C 标准库提供的链表容器避免重复造轮子。下面演示std::list的常用操作。#include iostream #include list using namespace std; int main() { // 创建双向链表 listint myList; // 尾部插入 myList.push_back(10); myList.push_back(20); myList.push_back(30); // 头部插入 myList.push_front(5); // 遍历输出 cout 链表元素: ; for (int val : myList) { cout val ; } cout endl; // 在指定位置插入在第二个位置插入 15 auto it myList.begin(); advance(it, 2); // 迭代器前进 2 步 myList.insert(it, 15); cout 插入 15 后: ; for (int val : myList) { cout val ; } cout endl; // 删除指定值 myList.remove(20); cout 删除 20 后: ; for (int val : myList) { cout val ; } cout endl; // 获取链表大小 cout 链表大小: myList.size() endl; // 判断是否为空 cout 是否为空: (myList.empty() ? 是 : 否) endl; return 0; }运行结果如下链表元素: 5 10 20 30 插入 15 后: 5 10 15 20 30 删除 20 后: 5 10 15 30 链表大小: 4 是否为空: 否std::list是双向链表支持 O(1) 的头部和尾部插入删除也支持在任意已知迭代器位置插入删除。需要注意的是std::list不支持随机访问不能直接用下标取值必须通过迭代器遍历。8. 链表常见变体除了最基本的单链表和双向链表还有几种常见的链表变体在不同场景下各有优势循环链表尾节点的 next 指向头节点形成环形结构。适合需要循环遍历的场景如操作系统的进程调度轮转、约瑟夫环问题。双向循环链表头节点的 prev 指向尾节点尾节点的 next 指向头节点。STL 的std::list内部就是这种结构便于从任意一端快速遍历。带头节点的链表在真正的头节点之前增加一个不存储数据的哨兵节点统一了空表和非空表的操作逻辑避免大量判空分支。跳表Skip List在有序链表上增加多层索引实现 O(log n) 的查找效率是 Redis 有序集合的底层实现之一。9. 总结链表是一种通过指针串联节点的线性数据结构它的核心价值在于动态内存管理和高效的插入删除操作。与数组相比链表牺牲了随机访问能力换来了更灵活的内存使用和更低的插入删除成本。在实际开发中选择数组还是链表需要根据业务场景判断如果以随机访问为主、数据规模相对固定优先选择数组如果数据规模动态变化大、插入删除频繁链表是更合适的选择。C 语言中需要手动管理节点内存C 则可以直接使用std::list或std::forward_list标准库容器兼顾效率和安全性。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻