『数据结构』跳跃表

本篇博客主要介绍一下跳跃表的原理和简单实现。

什么是跳跃表?


增加了向前指针的链表叫做跳表,跳表全称跳跃表,简称跳表。

  • 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表
  • 跳表在原有的有序链表上面增加了多级索引,通过索引实现快速查找
  • 跳表不仅能提高搜索性能,同时也可以提供插入和删除操作的性能

跳跃表的原理


我们知道,在一个单链表中如果想要根据值来查找一个元素,时间复杂度为 O ( N ) O(N) O(N)即使该链表是有序的,也不能通过二分的方式来降低时间复杂度
在这里插入图片描述
如上图,如果我们要查找值为52的元素的结点,我们必须从头结点开始,循环遍历知道找到值为52的结点,也就是最后一个结点,一共比较8次(-INF,负无穷不算)

我们有没有什么办法来减少访问的次数呢?我们可以试着去开辟一条捷径去访问52
在这里插入图片描述
如上图,我们要查找值为52的结点只需要在L2层查找4次即可找到
在这个结构中,查询值为47的结点只需要查询5次先在L2层查找47,查询4次后找到52,因为链表是有序的,所以我们回退到39,然后去L1层去查找,查询一次就查到了47,所以一共需要查找5次。

我们能不能让查找值为52结点的次数再减少呢?我们可以考虑再开辟一条捷径
在这里插入图片描述
如上图,我们查找值为52的结点只需要在L3层查找2次即可
查找元素47仍然是最耗时的,需要查询5次先在L3层查询2次,再在L2层查询2次,最后到L1层查询一次

从上面的查找过程看,这种思想和二分的思想非常相似,我们再将结构完善一下,最终结构如下
在这里插入图片描述
我们可以看出最耗时的访问仍然是访问值为47的结点一共需要查询6次。L4层访问52,L3访问25、52,L2访问39、52,L1访问47,共6次

跳跃表的时间复杂度


在这里插入图片描述
如果有n个元素,因为是二分,所以层数就应该是 l o g 2 N log_2N log2N层,再加上自身的1层

  • 以上图为例,如果是4个元素,那么分层为L4和L3,再加上本身的L2,一共3层;如果是8个元素,那么就是3 + 1层。

最耗时间的查询自然是访问所有层数,耗时 l o g 2 N + l o g 2 N log_2N + log_2N log2N+log2N,即 2 l o g 2 N 2log_2N 2log2N。为什么是 2 l o g 2 N 2log_2N 2log2N呢?

  • 我们以访问47来分析一下,查询到47要访问所有的分层,每个分层都要访问2个元素,中间元素和最后一个元素。

所以跳跃表的时间复杂度 O ( l o g 2 N ) O(log_2N) O(log2N)

跳跃表的实现分析


从上面跳跃表的时间复杂度的分析我们可以看出,该跳跃表是比较理想的
但是如果想要在跳跃表中插入或者删除一个元素呢?比如我们插入一个元素22、23、24…,自然是在L1层插入,但是这些元素在L1层插入之后,那么如何调整L2层和L3层的连接来维持这个比较理想的跳跃表呢
我们知道,平衡二叉搜索树的调整是一件令人非常头疼的事,左旋、右旋、左右旋;而调整一个理想的跳跃表将是一个比调整平衡二叉搜索树还复杂的操作
但是这里,我们不需要通过复杂的操作调整连接来维护这样完美的跳跃表有一种基于概率统计的插入算法,也能得到时间复杂度为 O ( l o g 2 N ) O(log_2N) O(log2N)的查询效率,这种跳跃表是比较实用的。

跳跃表的插入分析


从理想跳跃表,我们可以看出,L2层元素的数量是L1层元素数量 1 / 2 1/2 1/2L3层元素数量是L2层元素数量 1 / 2 1/2 1/2,依次类推。从这里,我们可以想到,只要在插入时尽量保证上一层的元素个数是下一层元素 1 / 2 1/2 1/2,我们的跳跃表就能成为理想的跳跃表
那么如何在插入时保证上一层元素的个数是下一层元素个数 1 / 2 1/2 1/2呢?我们可以通过抛硬币的方式来决定

  • 假设元素e要插入跳跃表,很显然,L1层肯定要插入元素e
  • 那么L2层要不要插入e呢?我们希望上层元素个数是下层元素个数的 1 / 2 1/2 1/2,所以我们有 1 / 2 1/2 1/2的概率希望e插入L2层,那么抛一下硬币吧,正面朝上就插入,反面就不插入
  • 那么L3到底要不要插入e呢?相对于L2层,我们还是希望 1 / 2 1/2 1/2的概率插入,可以继续抛硬币
  • 依次类推,元素e插入第i层的概率就是 ( 1 / 2 ) i (1/2)^i (1/2)i。这样,我们就能在跳跃表中插入一个元素了。
  • 如果插入元素的数量较小,结果很可能不是一个理想的跳跃表。但是如果元素数量非常大,根据概率学我们可以知道,最终的表结构肯定非常接近于理想的跳跃表

插入操作依赖于查询操作,所以插入操作的时间复杂度同样为 O ( l o g 2 N ) O(log_2N) O(log2N)

跳跃表的删除分析


删除操作和普通的链表删除操作完全一样,直接删除元素,然后调整一下删除元素后的指针就可以了
删除操作同样依赖于查询操作,所以删除操作的时间复杂度同样也是 O ( l o g 2 N ) O(log_2N) O(log2N)

代码实现


代码实现可参考

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值