链表的二分查找--跳表


前言

我们都知道有序数组快速查找某个数据有二分查找法,链表是否能否实现“二分”查找的算法?那就是本文提到的“跳表算法”。
和有序数组类似,如果要使用跳表算法,必须是有序的链表结构。

  • 支持快速的插入、删除、查找操作
  • 没有红黑树复杂
  • Redis 中的有序集合(Sorted Set)就是用跳表来实现的。

为什么链表开辟的内存空间不是连续的,对cpu缓存不友好,没办法利用cpu缓存预读数据,在内存数据访问上相比数组就效率没那么高了?
为了回答这个问题,先讲讲操作系统为什么要引入cpu缓存?随着硬件的发展,cpu的执行速度和内存的读取速度拉开了好几个数量级,因此高速cpu因等待低速内存读取数据而造成大量的cpu时间浪费,为了平衡这个速率,所以引入了比内存更高效的物理介质,也就是cpu缓存,于是cpu每次读取数据时,首先会到cpu缓存里读取,如果没有,就再从内存读取到cpu缓存,然后从cpu缓存里返回,每次从内存读取数据到cpu缓存时,并不是以某个数据为单位读取,而是以某个数据一定范围内的整块数据为单位读取,也就是以数据块单位读取,如果下次cpu执行中需要的数据刚好在上次cpu缓存加载进来的数据块中能找到,这时被直接从cpu缓存里读的数据也可以认为是上次cpu缓存预读的数据。因为不用去内存中读取数据了,也就间接缓和了高速cpu和低速内存的速率问题。
因为cpu缓存是以数据块单位从内存中读取数据的,如果要访问的是数组内的数据,数组的内存地址空间是连续的,所以cpu执行首次访问时首先cpu缓存内没有,就从内存中以数据块为单位读取,而这个数据块可能就涵盖了整个数组的所有数据,所以,cpu后面多次执行随机访问该数组内的数据时,就可以直接从cpu缓存里获取到数据了,整个访问速率肯定就很高效了。而链表内,因为每个节点开辟的空间都不是连续的,所以每次cpu缓存从内存里读取的数据块内可能只包含了链表数据中的某一个节点的数据,每次cpu访问链表中一个节点时,都没办法直接从cpu缓存里命中数据,而是需要从内存中读取,访问效率自然就没有数组高了。
————————————————
版权声明:本文为CSDN博主「凡沙-Fanrncho」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_31766907/article/details/107357047

对于链表查找优化

跳表简图

在这里插入图片描述

结构&优化

对于这样的单链表,如果要随机访问一个元素的时间复杂度为O(n).
在这里插入图片描述
在链表加入多级索引之后转化为跳表之后。随机访问的一个元素的时间复杂度为O(logn);

索引

多级索引如何产生
在链表的基础上,每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作索引或索引层。简图中的down 指针指向下一级节点;

时间复杂度计算

每两个结点会抽出一个结点作为上一级索引的结点,那第一级索引的结点个数大约就是 n/2,第二级索引的结点个数大约就是 n/4,第三级索引的结点个数大约就是 n/8,依次类推,第 k级索引结点的个数就是 n/(2^k);最上层节点为2个节点。

n/(2^k)=2 n=2* (2 ^ k) k=log2n;

在这里插入图片描述

插入数据

跳表中插入数据,不光要插入到原始链表,还需要在部分索引中插入节点。
那些索引中插入节点,由随机函数确定,随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。

  • 插入6的节点,随机生成的K为2
    在这里插入图片描述

时间复杂度

单链表中,一旦定位好要插入的位置,插入结点的时间复杂度是很低的,就是 O(1);跳表为了保证原始链表中数据的有序性,我们需要先找到要插入的位置,这个查找操作就会比较耗时。所以时间复杂度为查找元素的时间复杂度O(logn)

删除数据

删除的结点在索引中也有出现,我们除了要删除原始链表中的结点,还要删除索引中的
在这里插入图片描述

总结

  • 前提:有序链表
  • 由空间换时间的思想
  • 有序链表对于查询区间范围数据性能优异,查询起始点时间复杂度O(logN),普通链表为O(N)。

Redis 中的有序集合是通过跳表来实现的,严格点讲,其实还用到了散列表。不过散列表我们后面才会讲到,所以我们现在暂且忽略这部分。如果你去查看 Redis 的开发手册,就会发现,Redis 中的有序集合支持的核心操作主要有下面这几个:

  • 插入一个数据
  • 删除一个数据
  • 按照区间查找数据(比如查找值在 [100, 356] 之间的数据)
  • 迭代输出有序序列
    其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。
    当然,Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
    跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Abner G

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值