引言
在计算机科学中,数据结构的选择对于程序的性能至关重要。对于需要高效查找、插入和删除操作的场景,跳跃表(SkipList)是一种非常实用的数据结构。本文将详细介绍跳跃表的原理、实现和应用场景。
什么是跳表
下图是一个简单的有序单链表,假设我们需要查找10这个元素,只能从头部开始逐个遍历,无法随机访问,查找路径:1、2、3、4、5、6、7、8、9、10。这样的查找效率很低,平均时间复杂度很高O(n)。那有没有办法提高链表的查找速度呢?
如下图,我们从每5个元素抽出一个元素构建一个新的链表,新链表指向了原链表。这样我们查找数据的时候先从上层链表开始查找,那么查找10的路径为1、6、7、8、9、10。查询范围一下子缩小了一半
两层链表就把查询范围减少了一半,如果加多几层,岂不更快 ?
增加多一层链表后,查询路径为1、6、9、10,比两层更快。可以看出跳表是通过空间换时间的方式加快查询性能
跳表是一种基于有序链表的随机化数据结构,通过多级索引实现类似二分查找的效率。其核心思想是:用空间换时间,允许快速查询、插入和删除操作。跳表的平均时间复杂度为 O(log n),与平衡二叉树相当,但实现更简单
跳表的实现
插入操作
如下图,如果我们想把4这个元素插入到跳表中,那应该怎么操作呢?
插入元素和查找元素一样,先从最上层开始查找,找到底层原始链表应该插入的位置,跳表的原始链表需要保持有序。整个过程类似查找4原始一样,查找路径1、1、3、3。因为4大于3,小于5,4应该插入再3和5之间。
如果一直往原始链表插入数据,不更新索引(往上抽多层链表),就可能出现两个索引节点之间数据非常多,极端情况下,跳表就退化为单链表了。这种情况怎么解 ?那就要我们插入数据的时候,索引节点也要相对应的增加或者重建。
那怎么去维护这个索引呢 ?相对容易理解的方法就是每次新增节点把之前的索引删掉,再全部重新构建。这种效率是极低的,有没有更加高效的方法 ?
假设我们希望跳表每一层抽取50%元素作为上一层索引,理想的情况下第二层索引就是每隔一个元素抽取一个,第三层在第二层的基础上每隔一个元素抽取一个,以此类推。如下图所示
为了提高索引的维护效率,如果我们在原始链表中随机选择n/2 个元素作为一级索引,以此类推是否也可以呢? 虽然随机选择的元素作为索引可能是不均匀的,但是如果元素基数足够大,且抽取是随机的,那么索引整体就是均匀的,对于查询效率影响不大。
所以我们可以维护这么一个索引:随机选择n/2元素作为一级索引、n/4作为二级索引、n/8作为三级索引,以此类推,直到最顶层索引。下面就用代码来展示怎么实现
在每次插入新元素的时候,随机让元素有1/2概率在一级索引、1/4