之前我们知道,二分查找依赖数组的随机访问,所以只能用数组来实现。如果数据存储在链表中,就真的没法用二分查找了吗?而实际上,我们只需要对链表稍加改造,就可以实现类似“二分”的查找算法,这种改造之后的数据结构叫作跳表(Skip List)

1. 何为跳表?

对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低。

假如我们对链表每两个结点提取一个结点到上一级,然后建立一个索引指向原始结点,如下图所示。

这时候,我们要查找某一个数据的时候,就可以先在索引里面查找出一个大的范围,然后再下降到原始链表中精确查找。

比如,我们要查找 16,我们发现 16 位于 13 和 17 之间,这时候,我们就从 13 的地方下降到原始链表,然后再往后查询。原来我们查找 16,需要遍历 10 个结点,现在只需要遍历 7 个结点。

我们发现,加一层索引后,查找一个结点需要遍历的次数减少了,也就是查找效率提高了

那么我们再多加一级索引呢?效果会不会有更大提升?

这一次,我们只需要遍历 6 个结点了。

数据量不大的时候这种方法可能效率提高得还不是很明显,下面看一个包含 64 个结点的例子,这次我们建立了五级索引。

查找 62 的时候原来需要遍历 62 次,现在只需要 11 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显

2. 跳表查询的分析?

如果链表中总共有 $n$ 个结点,那么第一级索引就有 $\frac{n}{2}$ 个结点,第二级索引就有 $\frac{n}{4}$ 个结点,以此类推,那么第 $k$ 级索引就有 $\frac{n}{2^k}$ 个结点。如果最高级索引有 2 个结点,那总的索引级数 $k = log_2n - 1$,如果我们算上原始链表的话,那也就是总共有 $log_2n$ 级。

在第 $k$ 级索引中,假设我们要查找的数据为 $x$,当我们查找到 $y$ 结点时,发现 $y < x < z$ 时此时我们就要下降到 $k-1$ 级索引继续查找。在第 $k-1$ 级索引中,$y$ 和 $z$ 之间只有三个结点,因此,我们最多只需要查找 3 个结点。以此类推,每一级的索引最多都只需要遍历 3 个结点

而总的级别数为 $log_2n$,因此查找的时间复杂度就为 $3* log_2n = logn$。跳表查找的时间复杂度和二分查找一样,但这其实是以空间来换时间的设计思路。

跳表的所有额外索引结点总数为 $\frac{n}{2} + \frac{n}{4} + \frac{n}{8} + ... + 4 + 2 = n-2$,所以跳表的空间复杂度为 $O(n)$

但如果我们每三个结点建立一个索引,这时候额外需要的结点总数为 $\frac{n}{2}$,虽然空间复杂度依然为 $O(n)$,但减少了一半的索引节点存储空间。

实际上,在实际开发中,原始链表中存储的可能是很大的对象,而索引结点只需要存储关键值和几个指针,其额外占用的空间可以被忽略掉

3. 跳表高效的动态插入和删除?

在链表中,如果我们知道要插入数据的位置,那么插入的时间复杂度就为 $O(1)$。在跳表中,查找的时间复杂度为 $O(logn)$,因此,动态插入数据的时间复杂度也就是 $O(logn)$ 了。

从链表中删除结点的时候,如果结点在索引中也有出现,那么我们除了要删除原始链表中的结点,还要删除索引中的。

当我们不停地往跳表中插入数据的时候,如果我们不更新索引,就有可能出现某两个结点之间数据非常多的情况。极端情况下,跳表还会退化为单链表。

因此,我们需要某种手段来维护索引与原始链表大小之间的平衡,也就是说,如果链表结点变多了,索引值就相应地增加一些

当我们往跳表中插入数据的时候,我们可以选择同时也将这个数据插入到部分索引层中。而插入到哪些索引层中,则由一个随机函数生成一个随机数字来决定。如果这个数字为 K,那我们就将数据插入到第一级到第 K 级索引中。

4. 为什么 Redis 要用跳表来实现有序集合而不是红黑树?

Redis 中的有序集合支持的核心操作主要有以下几个:

  • 插入一个数据
  • 删除一个数据
  • 查找一个数据
  • 按照区间查找数据
  • 迭代输出有序序列

其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度和跳表是一样的。

但是,按照区间查找数据这个操作,红黑树的效率没有跳表高。跳表可以在 $O(logn)$ 时间复杂度定位区间的起点,然后在原始链表中顺序向后查询就可以了,这样非常高效。

此外,相比于红黑树,跳表还具有代码更容易实现、可读性好、不容易出错、更加灵活等优点,因此 Redis 用跳表来实现有序集合。

参考资料-极客时间专栏《数据结构与算法之美》

获取更多精彩,请关注「seniusen」!

数据结构和算法之——跳表相关推荐

  1. 数据结构与算法学习--跳表

    跳表 Skip List是一种随机化的数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间).基本上,跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机 ...

  2. 从零开始学数据结构和算法(二)线性表的链式存储结构

    链表 链式存储结构 定义 线性表的链式存储结构的特点是用一组任意的存储单元的存储线性表的数据元素,这组存储单元是可以连续的,也可以是不连续的. 种类 结构图 单链表 应用:MessageQueue 插 ...

  3. 黑马程序员 C语言数据结构与算法之线性表(链表/栈/队列/顺序表)

    C语言 链表基础知识清晰讲解(黑马) 讲的蛮好,就是音质不太好,有时听不清讲的啥! [黑马]数据结构与算法之线性表(链表/栈/队列/顺序表)[配套源码 嘛蛋,看错了,这是java的... 文章目录 链 ...

  4. rsa算法c语言实现_数据结构与算法之线性表-顺序表实现(C语言版本)

    原文托管在Github: https://github.com/shellhub/blog/issues/52 数据结构与算法之线性表-顺序表实现(C语言版本) 前言 数据结构与算法是一个程序员必备的 ...

  5. 数据结构与算法(一) 线性表之顺序表

     线性表是一种最简单.最常用的数据结构,根据存储方式可以分为顺序表和链表.  顺序表: 顺序表指的是用一组地址连续的存储单元依次存储线性表的数据元素,称为线性表的顺序存储结构或顺序映像(sequent ...

  6. java数据结构与算法之顺序表与链表深入分析

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52953190 出自[zejian的博客] 关联文章: java数据结 ...

  7. 数据结构和算法基础--线性表

    数据结构和算法基础–线性表 数据结构 = 数据的逻辑结构+数据的存储结构+数据的运算 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28ek7MfI-164242629 ...

  8. 数据结构与算法之线性表(超详细顺序表、链表)

    原创公众号:bigsai 文章已收录在 全网都在关注的数据结构与算法学习仓库 欢迎star 前言 通过前面数据结构与算法基础知识我么知道了数据结构的一些概念和重要性,那么我们今天总结下线性表相关的内容 ...

  9. Java数据结构和算法:线性表

    线性表的定义 线性表(linear-list)是最常用最简单的一种数据结构.一个线性表是n (n≥0)个相同类型数据元素的有限序列.记为: L= (a1, a2 , - , an ). 其中,L是表名 ...

最新文章

  1. 终端主题_再见 XShell 和 ITerm 2,是时候拥抱全平台高颜值终端工具 Hyper 了!
  2. 图灵奖得主Bengio再次警示:可解释因果关系是深度学习发展的当务之急
  3. 很好的Markdown开源库
  4. win10计算机无限弹网页,win10系统浏览网页时频繁弹出广告怎么办 Window10阻止网页弹出广告的四种方法...
  5. 软件开发依据的标准或法律法规_第178篇丨直真科技:官宣!定制软件开发不应该采用完工百分比法确认收入...
  6. php 查找点,在多边形PHP中查找点
  7. 无法加载文件 C:/Windows/Microsoft.NET/Framework/Meaningless_string/mscorlib.tlb
  8. Google 各语言网站合集
  9. [wbia 2.2] 对检索结果进行评估
  10. python算法系列资料集(三)
  11. 【数论】21蓝桥:货物摆放
  12. 如何将新的token发给前端比较好_前端工程师为什么要学习编译原理?
  13. Java思维导图(1)
  14. sql 获取日期时分秒_[转载]ASP.NET和SQLserver获取当前日期时间:年月日、时分秒...
  15. 数学建模之TOPSIS法
  16. 电子商务网站评价研究与应用分析
  17. 中国联通5G-NR 900MHz基站设备技术白皮书(2022)
  18. 卧槽!微信头像可以带圣诞帽啦!
  19. [附源码]java+ssm计算机毕业设计java磐基建筑机械租赁有限公司机械租赁系统41c32【源码、数据库、LW、部署】
  20. 第八部分 项目资源管理

热门文章

  1. hive执行流程(3)-Driver类分析1Driver类整体流程
  2. linux开机流程、模块管理与Loader(续)
  3. java获取昨天日期
  4. MS SQL SERVER 2005 用于Web开发的安装注意事项
  5. 单片机控制24v电压_最全变频器控制端子接线方法和技巧
  6. 服务认证暴力破解工具Crowbar
  7. 卡尔曼滤波对gps轨迹数据清洗_卡尔曼滤波:从入门到精通
  8. 屏幕显示密度dpi_PPI和DPI有什么区别?
  9. c++fabs函数_二次函数背景下的菱形存在性问题
  10. iOS将文件在自己App中打开