跳跃表(Skip List)是一种 基于链表的数据结构,用于加速查找、插入、删除等操作。它通过 多级链表 来实现快速的元素查找,具备类似于平衡树的特性,但实现方式更简单。跳跃表常用于数据库、搜索引擎(如 Elasticsearch、Lucene 等)中的倒排索引,以加速数据访问。

1. 跳跃表的基本结构

跳跃表可以看作是在普通 有序链表 上附加了 多级索引层。普通的有序链表查找某个元素时,需要从头遍历,时间复杂度为 O(n),而跳跃表通过增加索引层,可以使查找操作的时间复杂度接近 O(log⁡n)。

跳跃表结构的组成:
  • 底层链表(Level 0):这是一个普通的 升序链表,包含了所有元素。跳跃表的其他层是为这条链表加速的辅助层。
  • 上层索引链表(Level 1, Level 2, …):上层链表由部分元素组成,每一层的元素是下一层链表的 子集。上层链表的长度通常比下一层链表短,通过在上层进行大步跳跃,可以加速到目标元素的查找。
举个例子:

假设我们有一个跳跃表,它包含以下元素:3、6、7、9、12、17、19、21、25、26、30、35。

跳跃表的层级可以是:

Level 2:      3            12                25  
Level 1:      3      7      12        19      25      30 
Level 0:  3   6   7   9  12   17   19   21   25   26   30   35
  • Level 0:是完整的链表,包含所有元素,查找复杂度为 O(n)O(n)O(n)。
  • Level 1:每隔几个元素选取一个节点,用于快速跳跃。
  • Level 2:包含更少的节点,以更大步长进行跳跃。

2. 跳跃表的查找过程

跳跃表的查找过程类似于在层次结构中“跳跃”查找,具体步骤如下:

  1. 从最高层(Level 2)开始:跳跃表从最高层开始查找。因为最高层的元素较少,查找可以大步向前跳过多个元素。
  2. 向前跳跃,直到到达不大于目标的最大值:例如要查找 19,从 Level 2 的 3 -> 12 继续向前跳跃,直到 25,发现 25 大于 19,则退回到 12
  3. 逐层向下,缩小范围:在 12 之后,切换到下一层(Level 1),从 12 -> 19 继续前进,找到了目标元素。
  4. 如果需要更精细的跳跃,继续到最低层:如果在上一层没有找到目标,切换到最底层(Level 0),从当前位置继续向前,最终找到目标元素。
查找示例:
  • 查找目标 19
  • 在 Level 2,从 3 -> 12,再到 25,发现 191225 之间,因此向下切换到 Level 1。
  • 在 Level 1,从 12 -> 19,找到目标元素。

这种跳跃查找的过程将单层链表的线性查找时间从 O(n) 优化到 对数级别 O(log⁡n),因为每一层的链表长度按指数级缩短。

3. 跳跃表的构建和插入

  • 构建:跳跃表的构建是通过从底层链表开始,按一定概率(如 1/2 或 1/4)随机将元素提升到上层索引中。通常的做法是每个元素以 50% 的概率出现在上一层,从而形成多级跳跃表。
  • 插入:插入一个新元素时,跳跃表会决定该元素需要出现在的层数,并按照该层数将其插入各层的对应位置。插入时也从上层往下查找合适的位置,并维护每层链表的有序性。
插入示例:

假设要在上面示例的跳跃表中插入元素 8

  1. 从最高层 Level 2 开始,3 -> 12,然后切换到 Level 1。
  2. 在 Level 1,3 -> 7 -> 12,在 712 之间找到合适的插入位置,切换到 Level 0。
  3. 在 Level 0,7 -> 9,最终在 79 之间插入 8
  4. 然后按概率决定是否在上层插入 8

4. 跳跃表的删除

删除操作与查找类似:

  1. 从上到下查找要删除的元素:先从最高层开始查找目标元素的位置,一旦找到元素,删除它,并向下层进行检查,直到底层链表,确保在所有层级都删除目标元素。
  2. 删除后维护链表结构:确保每一层依旧是有序链表。

5. 跳跃表的性能分析

  • 时间复杂度:跳跃表的查询、插入、删除操作的平均时间复杂度为 O(log⁡n),因为每一层索引链表的长度是指数级减少的,从而减少了需要遍历的元素个数。最坏情况下时间复杂度为 O(n)(如果只有一层,退化为普通链表)。
  • 空间复杂度:跳跃表的空间复杂度为 O(n),虽然有多层链表,但每一层的元素个数是底层链表的一部分,因此总体空间占用是线性增长的。

6. 跳跃表与其他数据结构的比较

  • 跳跃表 vs. 平衡二叉树(如 AVL 树、红黑树)
  • 跳跃表和平衡二叉树的查找、插入、删除的时间复杂度都是 O(log⁡n)。
  • 跳跃表的实现更简单:相较于平衡树复杂的旋转操作,跳跃表通过随机层级的方式实现平衡。
  • 空间消耗较高:跳跃表占用了额外的空间存储多层链表,而平衡树只需存储树节点。
  • 跳跃表的随机性:跳跃表基于随机数来决定元素所在的层级,而平衡树有严格的平衡条件,需要频繁的旋转调整。

7. 跳跃表在 Elasticsearch 和 Lucene 中的应用

在 Elasticsearch 和 Lucene 中,跳跃表被用于 加速倒排索引的查询,特别是在面对长倒排列表时。每个词对应的文档列表可能非常长,为了避免逐个文档 ID 进行遍历,Lucene 会为每个倒排列表构建跳跃表。

  • 快速跳跃:当执行词项查询时,Lucene 通过跳跃表跳过无关的文档 ID,从而快速定位相关文档。
  • 效率提升:跳跃表帮助 Lucene 在处理长倒排列表时依然保持高效,尤其是在处理大规模数据时,跳跃表能够大幅减少查询的计算量。

总结

跳跃表是一种高效的、基于链表的数据结构,通过多级索引层实现了快速查找操作。它以简单的随机算法代替了复杂的平衡树结构,提供了接近 O(log⁡n)的查找性能,并且在数据库、全文搜索等场景中有广泛应用。特别是在倒排索引中,跳跃表的跳跃查找机制可以加速大规模数据的查询操作。