Article · 2026-09-22

Skip List:为什么多加几层链表就能加快查找

从普通有序链表出发,理解 Skip List 的多层索引、查找过程以及概率化设计。

Data StructureSkip ListAlgorithm

从有序链表的问题开始

第一次看到 Skip List,也就是跳表时,我觉得它的结构有一点奇怪。它不像二叉搜索树那样有明确的左右子树,也不像哈希表一样通过哈希函数直接定位元素,而是在普通链表上面又增加了很多层链表。

要理解 Skip List,可以先从一个普通的有序链表开始。

假设链表中保存:

1 → 3 → 7 → 12 → 18 → 25 → 31 → 40

如果要查找 31,只能从头开始不断向后移动:

1 → 3 → 7 → 12 → 18 → 25 → 31

虽然数据已经有序,但是链表本身不能像数组一样通过下标直接访问中间位置,因此有序并没有让查找快很多。

最坏情况下仍然需要遍历整个链表,时间复杂度是:

O(n)

Skip List 的核心想法就是:既然每次一个节点一个节点地走太慢,那么能不能在链表上增加一些“快速通道”?

给链表增加索引

例如从原始链表中抽出一部分节点:

Level 1:

1 -------- 7 -------- 18 -------- 31

Level 0:

1 → 3 → 7 → 12 → 18 → 25 → 31 → 40

现在查找 31 时,可以先沿着 Level 1 向右走。

从 1 跳到 7,再跳到 18,再跳到 31,就不需要访问底层所有节点。

如果数据更多,还可以继续往上建立索引:

Level 2:

1 ---------------- 18

Level 1:

1 -------- 7 ----- 18 -------- 31

Level 0:

1 → 3 → 7 → 12 → 18 → 25 → 31 → 40

这时候 Skip List 已经有点类似二分查找的感觉了。

查找时先从最高层开始。如果下一个节点还没有超过目标值,就向右移动;如果继续向右会超过目标值,就下降一层继续查找。

因此整个过程可以概括为:

能向右就向右
不能向右就向下

直到到达最底层。

为什么不用固定规则建立索引

这里还有一个很有意思的问题:

哪些节点应该出现在上一层?

一种直观的做法是每隔两个节点抽一个节点,但是这样一来,每次插入或删除节点后,都可能需要重新调整大量索引。

Skip List 选择了另一种方式:随机。

当插入一个新节点时,它首先一定出现在最底层,然后通过随机过程决定它是否继续出现在更高一层。

例如可以规定:

50% 的概率升到 Level 1
50% 的概率继续升到 Level 2
50% 的概率继续升到 Level 3
……

因此一个节点的层数可能是:

1 层
2 层
3 层
4 层
……

层数越高的节点通常越少。

从整体来看,就会形成一种逐渐稀疏的结构:

Level 3:  1 ------------------------------ 31
Level 2:  1 ------------- 18 ------------- 31
Level 1:  1 ----- 7 ----- 18 ----- 25 ---- 31
Level 0:  1 → 3 → 7 → 12 → 18 → 25 → 31 → 40

它并不保证每一层绝对均匀,但是从概率上看,通常可以保持比较合理的分布。

这也是 Skip List 和 AVL Tree、Red-Black Tree 一个很大的区别。

平衡树通过旋转等操作严格维持结构平衡,而 Skip List 不强制维护严格平衡,而是依赖随机概率得到一个“总体上足够好”的结构。

查找复杂度

如果这些索引层分布比较合理,那么每下降一层,都可以排除掉相当一部分节点。

因此 Skip List 的平均查找复杂度可以达到:

O(log n)

插入和删除也类似。

首先通过 Skip List 找到目标位置,然后只需要调整对应层上的指针,所以平均时间复杂度同样为:

查找:O(log n)
插入:O(log n)
删除:O(log n)

不过这里需要注意一个关键词:平均。

Skip List 是一种概率型数据结构。

极端情况下,如果所有节点都没有生成高层索引,那么它就会退化成一条普通链表,此时查找复杂度会变成:

O(n)

只是这种情况出现的概率通常很低。

插入一个节点发生了什么

例如现在要插入 20。

首先按照正常查找过程找到:

18 < 20 < 25

所以底层变成:

18 → 20 → 25

接下来随机决定 20 能达到多少层。

假设它最终拥有三层,那么还需要把它插入 Level 1 和 Level 2:

Level 2: 18 ------ 20 -------- 31

Level 1: 18 ------ 20 -- 25 -- 31

Level 0: 18 → 20 → 25 → 31

实际上 Skip List 在查找插入位置时,通常会顺便记录每一层最后一个小于目标值的节点。

插入时就可以直接修改这些节点的 next 指针,而不需要重新遍历一遍。

Skip List 和其他结构的区别

Skip List、Hash Table 和平衡搜索树解决的问题有一些重叠,但是特点并不完全一样。

Hash Table 的平均查找速度通常可以达到 O(1),非常适合根据一个确定的 key 查找数据,但是它天然不擅长维护数据的有序关系。

例如:

找 key = 100

哈希表非常适合。

但是如果问题变成:

找到 100 到 200 之间的所有元素

有序结构通常更加方便。

而 Skip List 本身就是按照 key 的顺序组织节点,所以范围查询比较自然。

和平衡树相比,Skip List 的理论最坏情况没有那么漂亮,但是它的实现思路比较直接。主要操作基本都是:

比较 key
移动指针
修改 next

不需要处理复杂的树旋转和重新平衡。

我的理解

我现在更愿意把 Skip List 看成一种“用空间换跳跃距离”的结构。

普通链表只有一条路:

一个一个往后走

Skip List 则在原链表上建立了多级高速通道:

先走高速层
→ 接近目标
→ 下到更细的一层
→ 最后进入底层定位

它最有意思的地方并不是“多层链表”本身,而是它没有通过复杂规则维护绝对平衡,而是利用概率获得接近 O(log n) 的平均性能。

这也是一种很值得学习的数据结构设计思想:

有时候并不需要维护一个严格完美的结构,只需要保证它在概率意义上足够好,就可以用更简单的实现得到很不错的性能。