论文地址:https://15721.courses.cs.cmu.edu/spring2018/papers/08-oltpindexes1/pugh-skiplists-cacm1990.pdf

关键点:

1、在算法内部引入随机性,从而避免对插入顺序随机性的依赖

2、如何插入和删除一个元素,同时更新前驱节点的第k跳指针

3、如何为一个新增的node分配一个合适的level

4、MaxLevel应该选多少

综述

跳表使用一种概率上的平衡而非强制平衡(相较于balance tree),使得插入和删除操作比同类的平衡算法(balance tree)更低。

前言

binary tree可用于表示抽象的数据结构,例如字典、有序列表等。当元素为按随机顺序插入时,binary tree可以很好的工作,但当元素按顺序插入时,性能急剧退化。如果在插入前,可以对插入的元素进行随机化排序,那么就可以使得binary tree拥有良好的性能。但是由于binary tree通常需要在线响应动作(In most cases queries must be answered on-line),因此提前将待插入元素随机化是不切实际的。balance tree算法会在执行树的操作时,re-arange树的形状,使得树始终保持某种平衡规则(例如左子树与右子树高度之差不超过1),以保持树的检索性能。

跳表是平衡树的一种概率替代方案。

跳表通过咨询(consulting??)一个随机数生成器来实现平衡。(Skip lists have balance properties similar to that of search trees built by random insertions, yet do not require insertions to be random.)

(既然不能保证insert的顺序是随机的,那么就在算法内部引入一个随机来达到保持平衡的效果)

SKIP LISTS

当搜索一个linked list时,我们需要遍历这个linked list中的每个元素。时间复杂度O(N)。

若链表是有序的,并且每个node上都保存了到达下两跳节点的指针(has a pointer to the node two ahead it in the list),那么我们最多只需要搜索个节点。

同理,若我们保存了下四跳的节点,那么需要搜索的节点数就减少至个。

若每第个节点,都有指向第跳节点的指针,那么在linked list中查找元素时需要检索的节点数量将减少至个,而指针的数量只翻了一倍。这样的数据结结构将拥有高效的搜索速度,但是对这样的数据结构进行插入或删除操作几乎是不切实际的(因为每插入或删除一个元素,都需要重新调整下x跳指针指向的node)。一个节点若包含若指向下K个节点的指针,则我们称它为level K node。如果每第个节点都包含后续第个节点的指针,则每类levels节点的比率分布是固定的:

50%的节点包含level 1,25%的节点包含level 2,12.5%的节点包含level 3,以此类推。

若每个节点的level是随机生成的(但每层的节点概率仍然按照上述比例50%、25%、12.5%...进行分配),会发生什么事情呢?

SKIP LIST ALGORITHMS

这一章节会介绍算法的搜索、插入和删除操作。

搜索:返回某个key对应的value,若key不存在则返回fail。

插入:将制定的key与new value对应起来(若key尚不存在,则新增一个node)。

删除:删除制定的key。(疑问:删除一个node时,如何更新指向这个node的前置节点的level pointer???)

诸如“查找最小key”、“查找下一个key”等额外操作都很容易实现。

跳表中的每一个元素,都用一个node来表示。每个node的层数都是随机生成的,不依赖于当前结构中包含的元素个数。(Each element is represented by a node, the level of which is chosen randomly when the node is inserted without regard for the number of elements in the data structure. )

初始化(Initialization)

初始化一个list,下一跳为nil,当前最大level为1。

搜索算法(Search Algorithm)

按当前节点的leve高pointer向level1 pointer搜索,若forward[i].key < searchKey,则换forward[I]的node继续尝试,直到找到(return value)或找不到(fail)时返回。

插入和删除算法(Insertion and Deletion Algorithms)

在执行插入和删除操作时,算法只是执行简单的搜索和拼接动作。例如:

插入算法

(1)从skip list的header开始,先搜索到比searchKey小的最靠右的node,并存储在update[i]中(i表示层数)

如上图实例中,在完成search时,update中的值为:

update[LEVLE4] = node6

update[LEVEL3] = node6

update[LEVEL2] = node9

update[LEVEL1] = node12

(2)判断node->forward[1]的key是否等于searchKey,若等于则说明key已经存在,则直接更新value=newValue后即可返回;否则,进入(3)

(3)新增一个node,过程如下:

(a)随机生成一个level  lvl(但概率还是按照上文提到的比率),若新lvl比现有的最大level高,则执行(a.1)(a.2)

(a.1)将update[currLevel]到update[lvl]数组元素的值都更新为list->header

(a.2)更新list->level为这个新生成的lvl。

(b)new一个新的node X,入参为lvl、searchKey、value

(c)更新node X各level的指针

(c.1) X->forward[i] = update[i]->forward[i]

此时,按上述例子,各个节点的forward信息更新为(因为node17随机生成到的level是2,所以只会有level2和level1的forward指针)

node17->forward[LEVEL2] = update[LEVEL2]->forward[LEVEL2] = node9->forward[LEVEL2] = node25

node17->forward[LEVEL1] = update[LEVEL1]->forward[LEVEL1] = node12->forward[LEVEL1] = node19

(c.2) update[I]->forward[i] = X

此时,按上述例子

update[LEVEL2] = node9,  node9->forward[LEVEL2] = node17

update[LEVEL1] = node12,  node12->forward[LEVEL1] = node17

插入算法伪代码

删除算法

(1)如同上述插入算法,也是先检索到searchKey所在的NodeX,并且在检索过程中,生成一个udpate[i]向量

(2)对于level 1 -》K (K为当前跳表中最大的有效层数)

(a) 若update[level i]->forward[level i] == nodeX,  则update[level i]->forward[level i] = x->forward[level i];

(b) 若update[level i]->forward[level i] != nodeX,  则 break循环。(因为update[level i]->forward[level i]不为nodeX,说明NodeX的level层数不够高,update[level i]->forward[level i]指向了nodeX之后的其他节点,因此可以break了)

(3)free nodeX

(4)遍历list->level到1,若list->header->forward[level i] == NIL了,则将list->level减一

例如,想要删除node17,那么:

(1)完成search操作后的update向量为

update[4] = node6

update[3] = node6

update[2] = node9

update[1] = node12

(2) 遍历更新前向节点的下k跳节点

update[1] = node12, node12->forward[1] = node17->forward[1] = node19

update[2] = node9, node9->forward[2] = node17->forward[2] = node25

update[3] = node6, node6->forward[3] = node25 > node 17, break

(3) free node17

(4) 因为node17的level只有2,所以不会导致List->level的变化

我们再尝试删除一下node6,过程为:

(1)完成search操作后的update向量为

update[4] = list->header

update[3] = list->header

update[2] = list->header

update[1] = node3

(2) 遍历更新前向节点的下k跳节点

update[1] = node3, node3->forward[1] = node6->forward[1] = node7

update[2] = list->header, list->header->forward[2] = node6->forward[2] = node9

update[3] = list->header, list->header->forward[3] = node6->forward[3] = node25

update[4] = list->header, list->header->forward[4] = node6->forward[4] = NIL, break

(3) free node6

(4) 更新list->level

此时,list->level == 4

for i:= 4 to 1

list->header[4] == NIL, list->level = 4-1 = 3

list->header[3] == node25, break

此时,list->level == 3

删除算法伪代码

选择一个随机level (Choosing a Random Level)

计算随机level的伪代码(按上述例子,p=1/2)

因为p=1/2,所以我们可以用抛硬币的场景来模拟一下。

lvl的初始值是1,而想让lvl+1的条件是我必须能random到一个小于0.5的值(假设为抛到硬币的正面吧)

那么,若我希望lvl == 2,则意味着我必须两次抛到硬币的正面才行,那么概率就是0.5*0.5

若我希望lvl == 3, 则必须连续三次抛到硬币的正面,则概率为 0.5*0.5*0.5

依次类推,若希望得到更高的lvl,则需要连续抛到正面的次数要求越多,概率也随之降低。

应该从第几层检索(At what level do we start a search? Defining L(n))

按照刚刚的随机level生成器,对于一个有16个节点的linked list,我们可能得到这样的结果

level1 有9个节点

level2 有3个节点

level3 有3个节点

level14 有1个节点   (概率非常非常低,需要连续13次抛出硬币的正面)

若我们每次搜索都从level 14开始,那么会有很多无用功。(因为level 4-13都是只有一个节点)。

那么,我们应该从哪个level开始做搜索呢?

理想情况下,为下文描述方便此公式简写为L(n)

当list中有一个元素拥有异常巨大的level时,我们通常有如下几种应对方法.

策略一: Don’t worry, be happy.

就按list->level来搜索,某个节点的level远远大于L(n)的概率是极地极低的。

策略二:  Use less than you are given.

尽管对于level14的node,我们有14个forward指针,但是可以将其优化到仅使用L(n)个指针。不过这个优化的成本很高,但收益很小,因此不推荐用这种方法。

策略三:Fix the dice

把筛子固定下来,对于每个newNode,都简单粗暴的将它的level定义为当前最大level+1。

但是这样会导致算法的随机性被打破。

(那能否每次随机生成level时,maxLevel等于当前最大有效level+1呢?这样一来,可以缓慢的趋近预设的MAXLevel,而不至于中间出现很多断层的level)

MaxLevel取多少合适(Determining MaxLevel)

直接给结论吧

MaxLevel = L(N) (where N is an upper bound on the number of elements in a skip list).

对于p=1/2 , MaxLevel = 16的跳表,可以存储216个元素。

Skip Lists: A Probabilistic Alternative to Balanced Trees 阅读笔记的更多相关文章

  1. 讲讲跳跃表(Skip Lists)

    跳跃表(Skip Lists)是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且在实现上比平衡树要更为 ...

  2. 【转载】 《Human-level concept learning through probabilistic program induction》阅读笔记

    原文地址: https://blog.csdn.net/ln1996/article/details/78459060 --------------------- 作者:lnn_csdn 来源:CSD ...

  3. 论文阅读笔记五十三:Libra R-CNN: Towards Balanced Learning for Object Detection(CVPR2019)

    论文原址:https://arxiv.org/pdf/1904.02701.pdf github:https://github.com/OceanPang/Libra_R-CNN 摘要 相比模型的结构 ...

  4. 论文阅读笔记(二十二)【CVPR2017】:See the Forest for the Trees: Joint Spatial and Temporal Recurrent Neural Networks for Video-based Person Re-identification

    Introduction 在视频序列中,有些帧由于被严重遮挡,需要被尽可能的“忽略”掉,因此本文提出了时间注意力模型(temporal attention model,TAM),注重于更有相关性的帧. ...

  5. Skip list--reference wiki

    In computer science, a skip list is a data structure that allows fast search within an ordered seque ...

  6. 用golang实现常用算法与数据结构——跳跃表(Skip list)

    背景 最近在学习 redis,看到redis中使用 了skip list.在网上搜索了一下发现用 golang 实现的 skip list 寥寥无几,性能和并发性也不是特别好,于是决定自己造一个并发安 ...

  7. 探索Skip List (跳跃表)

    附William Pugh的论文 Skip Lists: A Probabilistic Alternative to Balanced Trees 写在前面 以下内容针对的是Skip List的插入 ...

  8. Redis入门指南(第2版) Redis设计思路学习与总结

    https://www.qcloud.com/community/article/222 宋增宽,腾讯工程师,16年毕业加入腾讯,从事海量服务后台设计与研发工作,现在负责QQ群后台等项目,喜欢研究技术 ...

  9. [转载] 跳表SkipList

    原文: http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html leveldb中memtable的思想本质上是一个skiplist ...

随机推荐

  1. python之线程池和进程池

    线程池和进程池 一.池的概念 池是用来保证计算机硬件安全的情况下最大限度的利用计算机 它降低了程序的运行效率但是保证了计算机硬件的安全从而让你写的程序能够正常运行 ''' 无论是开设进程也好还是开设线 ...

  2. OpenCV Java Tutorials- Camera Calibration

    2020-10-10原文地址:https://opencv-java-tutorials.readthedocs.io/en/latest/09-camera-calibration.html#id1 ...

  3. 手撸ORM浅谈ORM框架之基础篇

    好奇害死猫 一直觉得ORM框架好用.功能强大集众多优点于一身,当然ORM并非完美无缺,任何事物优缺点并存!我曾一度认为以为使用了ORM框架根本不需要关注Sql语句如何执行的,更不用关心优化的问题!!! ...

  4. 联赛%你测试10T2:漫无止境的八月

    题意: 思路: 有几个特殊的性质: 在不考虑q里面的单点修改,我们先只判断一个序列是否Yes. 我们注意到每次操作都是对一个长度为k的区间进行区间加减1的操作,所以我们如果将序列里面的数按%k分组,把 ...

  5. lumen发送邮件配置

    composer.json 增加 "illuminate/mail":"5.6"composer update -vvvconfig 目录新建mail.php ...

  6. Java实现:抛开jieba等工具,写HMM+维特比算法进行词性标注

    一.前言:词性标注 二.经典维特比算法(Viterbi) 三.算法实现 四.完整代码 五.效果演示: 六.总结 一.前言:词性标注 词性标注(Part-Of-Speech tagging, POS t ...

  7. jq ajax封装

    //ajax公共方法,zs 2017-06-14 $.extend({ //比jq的ajax多了的参数: //salert是否在请求成功后弹出后台的SuressStr字段值 //ealertStr:请 ...

  8. Linux系统部署WEB项目(2020最新最详细)

    2020最新Linux系统发行版ContOS7演示部署WEB项目 为防止操作权限不足,建议切换root用户,当然如果你对Linux命令熟悉,能够自主完成权限更新操作,可以不考虑此推荐. 更多命令学习推 ...

  9. linux ssh自动输入密码,expect使用

    想搞一个使用ssh登录批量ip地址执行命令,自动输入密码的脚本,但是ssh不能使用标准输入来实现自动输入密码,于是了解到了expect这个可以交互的命令 是什么 查看使用man查看expect,是这么 ...

  10. maven打包插件

    如何把依赖的jar包中的资源抽到当前jar中 maven-compiler-plugin:编译插件,可指定资源jdk版本,前提是当前代码使用的jdk版本 大于或等于 source maven-asse ...