数据结构 —— 链表

在数据结构体系中,顺序表与链表是两大最基础的线性存储结构 。顺序表依靠连续内存 实现随机访问,但插入、删除中间元素效率低下;而链表用离散内存 + 指针连接的方式,完美解决了顺序表的痛点,是 Linux 内核、操作系统、网络编程中高频使用的数据结构。

本文从零拆解链表的底层原理、分类、完整实现、核心优缺点、与顺序表的对比,同时结合 Linux 内核使用场景,彻底吃透面试高频考点。


一、先搞懂:什么是链表?

1.1 链表的核心定义

链表是非连续、非顺序的线性表,它不要求内存连续,通过 ** 指针(引用)** 将分散在堆内存中的一个个节点串联起来。

  • 每个节点包含两部分:数据域 (存储业务数据)+ 指针域(存储下一个节点的内存地址)
  • 节点分散在内存各处,不连续排列,靠指针维系前后关系
  • 没有下标,不支持随机访问,只能从头节点开始逐个遍历

你可以把链表想象成一串带钩子的珠子:每个珠子存数据,同时带一个钩子勾住下一颗珠子;珠子不用挨在一起,靠钩子串联;想要找到第 N 颗珠子,必须从第一颗顺着钩子依次找。

1.2 链表的四大分类

日常开发 & 面试中,链表分为 4 种,层层递进:

  1. 单链表:最基础,只有后继指针,只能从头往后遍历
  2. 双向链表:有前驱 + 后继两个指针,可双向遍历,Linux 内核高频使用
  3. 循环链表:尾节点指针指向头节点,首尾相连
  4. 双向循环链表:双向 + 循环,功能最全,内核 list_head 底层就是它

1.3 链表与顺序表的核心对立点

顺序表:连续内存、随机访问快、中间增删慢、缓存友好链表:离散内存、随机访问慢、任意位置增删快、缓存不友好


二、单链表底层原理与完整实现

单链表是链表的基础,我们用 C++ 手写一个极简单链表,彻底搞懂底层逻辑。

2.1 节点结构定义

cpp 复制代码
// 链表节点
template <typename T>
struct ListNode {
    T val;              // 数据域:存储数据
    ListNode* next;     // 指针域:存下一个节点地址
    ListNode(T v) : val(v), next(nullptr) {}
};
  • val:存放业务数据
  • next:指向下一个节点,末尾节点 next 为nullptr

2.2 单链表核心操作

1. 头插(O (1))

在头部插入节点,只需要修改指针,是链表效率最高的插入方式。

cpp 复制代码
void push_front(T val) {
    ListNode<T>* new_node = new ListNode<T>(val);
    new_node->next = head;  // 新节点指向原头节点
    head = new_node;        // 头指针更新为新节点
}
2. 尾插(O (n))

需要遍历到链表尾部,才能插入节点。

cpp 复制代码
void push_back(T val) {
    ListNode<T>* new_node = new ListNode<T>(val);
    if(head == nullptr) { head = new_node; return; }
    // 遍历到尾节点
    ListNode<T>* cur = head;
    while(cur->next != nullptr) cur = cur->next;
    cur->next = new_node;
}
3. 指定位置插入(O (n))

找到前驱节点,修改指针指向,不需要移动任何元素

cpp 复制代码
void insert(int pos, T val) {
    // 找到pos前一个节点
    ListNode<T>* cur = head;
    for(int i=0; i<pos-1; i++) cur = cur->next;
    ListNode<T>* new_node = new ListNode<T>(val);
    new_node->next = cur->next;
    cur->next = new_node;
}
4. 删除节点(O (n))

找到前驱节点,跳过目标节点,释放内存即可,无需移动元素。

cpp 复制代码
void erase(int pos) {
    ListNode<T>* cur = head;
    for(int i=0; i<pos-1; i++) cur = cur->next;
    ListNode<T>* del = cur->next;
    cur->next = cur->next->next;
    delete del; // 释放堆内存,避免内存泄漏
}
5. 遍历查找(O (n))

链表没有下标,必须从头节点逐个往后遍历,无法直接跳转。

2.3 单链表致命缺点

  1. 只能单向遍历,无法反向查找
  2. 尾插 / 尾删效率极低,需要遍历整个链表
  3. 删除当前节点,必须找到它的前驱节点,操作繁琐

三、双向链表(Linux 内核最爱)

3.1 底层原理

双向链表在单链表基础上,给每个节点增加前驱指针 prev

cpp 复制代码
template <typename T>
struct DListNode {
    T val;
    DListNode* prev;    // 前驱指针:指向前一个节点
    DListNode* next;    // 后继指针:指向后一个节点
};
  • 每个节点可找到前驱、后继,支持双向遍历
  • 尾插、尾删、删除当前节点,效率直接提升到 O (1)
  • Linux 内核、epoll 就绪队列、进程调度队列,全部使用双向链表

3.2 核心优势

  1. 可正向、反向双向遍历
  2. 已知当前节点,可直接删除,无需遍历找前驱
  3. 头插、尾插、头删、尾删全部 O (1)

3.3 缺点

每个节点多存一个指针,内存开销比单链表大


四、循环链表 & 双向循环链表

4.1 循环链表

尾节点的next指针不再置空,而是指向头节点,首尾相连。

  • 优势:从任意节点都可遍历整个链表,适合环形场景(环形队列)
  • 缺点:遍历结束条件从next==nullptr变为next==head,容易死循环

4.2 双向循环链表(Linux 内核 list_head)

双向 + 循环,功能最全、使用最广。

  • 内核中实现:不存储数据,只存两个指针,嵌入任意结构体中,通用型极强
  • epoll 的就绪队列、进程 task_struct 调度队列、定时器队列,全部基于此实现

五、链表核心优缺点(面试必背)

优点

  1. 增删效率极高 :任意位置插入 / 删除节点,只需要修改指针,无需移动大量元素,O (1) 时间复杂度(已知节点)
  2. 内存利用率高:按需分配节点,没有顺序表的预留冗余内存,无内存浪费
  3. 内存离散存储:不需要连续的大块内存,碎片化内存也能使用,适配海量动态连接场景

缺点

  1. 不支持随机访问:只能顺序遍历,查找元素 O (n),速度远慢于顺序表
  2. 缓存命中率低:节点内存离散,CPU 缓存无法预加载,顺序遍历也比顺序表慢
  3. 额外内存开销:每个节点需要存储指针,内存占用比顺序表高
  4. 容易内存泄漏:节点手动 new 创建,忘记 delete 会造成内存泄漏

六、顺序表 vs 链表(终极对比,面试必背)

表格

特性 顺序表 链表
内存分布 连续内存 离散内存
随机访问 支持,O (1) 不支持,O (n)
头部 / 中间增删 O (n),需移动元素 O (1),仅修改指针
尾部增删 O (1)(均摊) 双向链表 O (1),单链表 O (n)
CPU 缓存 缓存友好,速度快 缓存不友好,速度慢
内存开销 低,无指针冗余 高,每个节点带指针
适用场景 查找多、增删少 增删多、查找少

场景选择口诀

  • 频繁查询、极少增删 → 选顺序表(vector)
  • 频繁插入删除、动态扩容 → 选链表
  • Linux 内核、高并发队列 → 选双向循环链表

七、链表在 Linux 网络编程中的实际应用

链表不是纸上谈兵,在你学习的 IO 复用、高并发服务器中,无处不在:

  1. epoll 就绪队列 :epoll 内核中用双向链表存储就绪的 fd 事件,epoll_wait 直接取链表头部,O (1) 获取就绪事件
  2. 进程 / 线程管理:Linux 内核用双向链表串联所有进程、线程,调度时快速增删节点
  3. 定时器管理:网络服务的定时器(心跳、超时检测),用链表管理超时事件
  4. 日志系统:日志缓冲区、异步队列,常用链表实现动态节点增删

八、高频面试总结

  1. 单链表:单向遍历,头插 O (1),尾插 O (n),实现简单,功能有限
  2. 双向链表:双向遍历,头尾操作 O (1),Linux 内核主流使用
  3. 循环链表:首尾相连,适合环形结构
  4. 双向循环链表:功能最全,epoll、内核调度核心数据结构
  5. 核心区别:顺序表胜在查询,链表胜在增删;顺序表缓存友好,链表动态灵活
相关推荐
图码6 小时前
二分查找进阶:如何在有序数组中快速找到Upper Bound?
数据结构·算法·面试·分类·柔性数组
Cthy_hy6 小时前
树状数组(BIT)进阶:差分优化实现区间修改、区间查询
数据结构·python·算法
代码中介商8 小时前
红黑树完全指南:从五条性质到完整插入删除实现
数据结构·算法
Sarvartha9 小时前
单链表的顺序建立与结点的删除(期末题复现)
数据结构
gumichef10 小时前
二叉树链式结构的实现
算法·链表·二叉树·队列
Dlrb121111 小时前
数据结构-链表
数据结构·链表·逻辑结构·单向链表·物理结构·valgrind工具
小的~~11 小时前
算法题:只出现一次的数字
数据结构·算法
一切皆是因缘际会11 小时前
从概率拟合到内生心智:七层投影架构重构AGI数字生命新范式
大数据·数据结构·人工智能·重构·架构·agi
历程里程碑11 小时前
56 . 高效ET非阻塞IO服务器设计指南
java·运维·服务器·开发语言·数据结构·c++·排序算法