从零开始学算法:7.跳表

栏目: 编程工具 · 发布时间: 6年前

内容简介:作者:业界都没有真正理解跳表。在公众号中回复“ACM模板”你将免费获得我大学耗时四年整理的《ACM算法模板》。

作者: tiankonguse | 更新日期:

业界都没有真正理解跳表。

在公众号中回复“ACM模板”你将免费获得我大学耗时四年整理的《ACM算法模板》。

如果大家想加入我的星球,可以加我微信,我免费给你拉进去,加我时暗号”算法星球”。

一、背景

大家好,我是tiankonguse。

由于某些原因,经常有人想学习算法但之前又没有相关经验,不知道从何做起。

我思考了许久,计划写一个系列来分享如何入门学习算法。

之前已经分享了《 认识算法 》、《 了解套路长啥样 》、《 排序算法 》、《 二分查找 》、《 链表 》,这是第七篇《跳表》。

一、基础知识

在上一篇文章《 链表 》最后我们通过分块把复杂度降到了O(sqrt(n)),还提到有一个方法可以做到O(log(n))的插入或删除复杂度。

这个数据结构就是跳表。

写完这篇文章时,发现一个重大秘密:跳表其实与顺序没有关系的,也就是跳表可以无序的。

业界都是用于维护有序序列,实际情况是跳表完全可以用于操作随机序列。

原来跳表才是一个真实的万能数组了,各种复杂度都可以接受,REDIS的LIST看来要重写了。

不过这里我们先以有序序列为例来讲解跳表,对于无序的,只需要忽略比大小操作就行了。

对于跳表是啥,其实不好描述,但是看图模拟操作一下,应该就可以马上明白他的原理了。

从零开始学算法:7.跳表 (图片来自维基百科)

首先可以看出来,我们有一个数组链表头,长度固定,并且表头的每个位置都是一个指针,指向后面。

对于每个位置,同样也有一个数组链表头,有不定的高度以及值信息,结构如下.

从零开始学算法:7.跳表

二、查找

看一个数据结构如何设计的,最简单的方式就是看查找的实现了。

跳表的查找步骤如下:

0.定义左边界为head

1.左边界从上到下依次遍历表头

2.发现是NULL代表当前层数没找到,层数下移

3.如果不是NULL,所在位置我们称为A,A对应的值和查找的值比大小

4.查找的值小于A,说明答案在A左边,从下一层数继续查找。

5.查找的值大于等于A,说明答案在右边,左边界更新为A,从当前层数继续查找(重点注意)。

6.如果遍历到最后一层了,还没找到,说明不存在。

可能看着上面的描述步骤太抽象,那我们举个具体的例子吧。

这里以查找5为例。

0.左边界为head,总共有4层 ,当前层数3

1.位置(head, 3)从第3层开始,遇到节点1,比较5大于1,左边界置为1

2.位置(1, 3)从第3层开始,遇到nul,层数下降

3.位置(1, 2)从第2层开始,遇到节点4,比较5大于等于4,左边界置为4

4.位置(4, 2)从第2层开始,遇到节点6,比较5小于6,层数下降

5.位置(4, 1)从第1层开始,遇到节点6,比较5小于6,层数下降

6.位置(4, 0)从第0层开始,遇到节点5,比较5大于等于5,左边界置为5

7.位置(5, 0)从第0层开始,遇到节点6,比较5小于6,层数下降

8.位置(5, -1)结束了

从零开始学算法:7.跳表

上面步骤的实现代码如下,可以发现,非常简单。

里面多了一个num, 用于计算偏移量,这样就可以知道查找值得坐标了。

从零开始学算法:7.跳表

三、插入

我们查找的时候,知道了节点的关键信息有三个:节点值、节点每层到下层的指针和距离。

现在我们需要在指定的位置插入一个值了,必然需要更新MAXLEVEL个链表,以及更新2*MAXLEVEL个链表间的距离。

从零开始学算法:7.跳表

如上图,假设我们要插入 5.5。

棕黄色的含义是当前节点到下个节点之间的节点个数,也就是我们储存的num字段。

浅蓝色的含义是当前位置之前有多少个节点,我们命名为preNum。

各层的蓝色线终点和preNum都可以在查找5.5的时候计算出来。

这样,我们插入5.5的时候,5.5的各层num与各层链表之前的num都可以通过简单的数学运算计算出来。

比如第3层前半段链表的num = 第0层的preNum(最长的蓝线)- 第三层的preNum(最短的蓝线) + 1.

而对于第三层后半段链表的num = 第三层的num(最长的黄线) - 第3层前半段链表的num(上面计算的值)

上面描述对应的代码就是下面这个函数,实现也不复杂。

有个地方需要说明一下,新插入的节点的高度是随机计算的,这样做的风险可能会导致这个算法退化为O(n)的复杂度。

从零开始学算法:7.跳表

四、删除

删除和插入是相反的操作,而且更简单。

找到位置后,直接把删除位置的num加到之前的节点,然后删除聊表即可。

这里就不多说了。

五、下标定位

由于储存了每个位置每层到下个next之间的节点数,这里就可以快速的判断是否可以跳过下个next。

跳过了更新总偏移量,没跳过则进入下一层,代码如下。

从零开始学算法:7.跳表

六、最后

知道了跳表节点储存的信息:值、各层到下个节点的指针和距离,我们就可以轻松的实现跳表了。

如果你没看明白,不要紧,多看几遍查找的步骤,你应该就能明白跳表的实现原理。

对于跳表,是个有序的序列,我们可以Log(n)的复杂度插入或删除一个数据,也可以在log(n)的复杂度内通过下标定位到对应的位置。

文章的开头说了,我们并不需要跳表有序。

我们只使用下标来增加数据或删除数据的话,跳表就是一个强大的数组了,而且平均复杂度也是O(n)的。

那为啥大家分享的跳表都是有序的呢?

第一个原因是无序的跳表意义不大,实现难度相比数组高出几个数量级,真要使用无序的了还不如使用其他的代替数据结构。

更关键的原因是业界都是使用跳表来实现有序集,从而做到快速查询。

比如 redis 、levelDB等开源软件都实现了有序跳表。

这样想来,无序跳表活得好憋屈,google了一下,竟然没有人使用这个方法。

redis如果使用跳表优化一下list一下的话,复杂度就可以从O(n)的复杂度提升到O(log(n))了。

震惊,我发现了一个log(n)的list数据结构,有序跳表,哈哈。

本文首发于公众号:天空的代码世界,微信号:tiankonguse-code。

推荐阅读:

从零开始学算法:7.跳表

今天长按识别上面的二维码,在公众号中回复“ ACM模板 ”,你将免费获得我大学耗时四年整理的《ACM算法模板》。

回复“ 算法的世界 ”,或点击 阅读原文 加入“tiankonguse的朋友们”,已有三百多个小伙伴加入。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First jQuery

Head First jQuery

Ryan Benedetti , Ronan Cranley / O'Reilly Media / 2011-9 / USD 39.99

Want to add more interactivity and polish to your websites? Discover how jQuery can help you build complex scripting functionality in just a few lines of code. With Head First jQuery, you'll quickly g......一起来看看 《Head First jQuery》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器