无序列表
看完你能亲手画出插入、删除、查找、遍历与扩容的每一步内存变化
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
无序列表
看完你能亲手画出插入、删除、查找、遍历与扩容的每一步内存变化
什么是无序列表
前一页看到列表由节点链接而成。现在有个关键问题:每个节点只知道下一个是谁,不记得自己是第几个——这和向量能直接按下标取元素完全不同。这一页就讨论由此带来的结构差异。
每张线索卡只指向下一张,想知道第几张只能一张张翻,卡上不印编号
循秩访问
上页说无序列表是节点手拉手串起来的,那已知第 5 个元素在哪?你没法像数组那样一步跳过去——只能从第一个节点出发,顺着引用一个一个数过去。这条逐个移动的路线,就叫循秩访问。
从第 1 节车厢开始逐节往后穿行,对应沿 next 引用逐个移动
循秩 vs 循位
「列表也能按下标访问」是常见误解——同样是第 k 项,取法完全不同。
- 只能从头沿引用逐个挪过去
- 取第 k 项需 k 次跳转,O(k)
- 插入删除只需改指针,O(1)
- 物理地址连续,秩即偏移量
- 取第 k 项一次算出地址,O(1)
- 插入删除需整体搬移,O(n)
查找的过程
在无序列表里找一个元素,只能从某个节点出发,沿引用一步步比对。
查找的实现
一段真实的 Python 线性查找:逐个比对,找不到就按约定返回 -1。
循环不变式:[0,lo) 已排除、[lo,hi) 待查。区间空即失败,统一返回 -1,调用方用返回值判断命中与否。
插入节点
在链表两个节点之间插入新节点,关键是先接后断,指针重接顺序不能错。
删除节点
摘除节点分四步:先定位,再重接,后清理,最后归还空间。
复制列表
从原列表尾部出发,每看一个节点就头插进新列表,循环到 p 为空。
析构与清空
看看无序列表如何逐个删掉节点,把内存干净归还给系统。
析构 = clear 循环逐个 remove 数据节点 + 最后删两个哨兵;每次 remove 摘链后 delete 归还内存,复杂度 O(n)。
唯一化
唯一化靠每个新节点向前查找,一旦遇到相同元素就当场删除,保证全表唯一。
自测一下
在无序列表的第 k 个位置找到节点并删除它,删除本身是 O(1),但整个操作为什么是 O(k)?
要点收束
- ✓列表的本质:逻辑相邻由指针维系,物理位置无关
- ✓循秩 O(n) 是一切操作复杂度的根源
- ✓查找/插入/删除:先定位 O(n),动作本身 O(1)
- ✓复制与析构是镜像:一个逐点 new,一个逐点 delete
- ✓唯一化 = 排序场景下双指针,无序场景下查找去重
课后思考
先自己想,别急着看参考答案——这些问题值得多琢磨一会。
参考答案顺着「循位访问」想:列表不支持循秩,排序需要随机访问才划算;在列表上排序反而更慢。提示:数据结构的选择决定了算法的形态。
参考答案插入多时列表占优:找到位置后 O(1) 挂接,数组要整体搬移。频繁按秩读取则反过来,数组 O(1) 循秩完胜列表的 O(n) 走链。
参考答案delete head 只释放头节点本身,后续节点全部泄漏。逐个走链释放是因为节点在堆上离散分布、彼此独立。边界:空列表要能直接安全返回。