第03章跳跃表SkipLists-精品

合集下载

详解Redis数据结构之跳跃表

详解Redis数据结构之跳跃表

详解Redis数据结构之跳跃表⽬录1、简介1.1、业务场景1.2、skiplist2、跳表2.1、跳表简介2.2、跳表层级之间的关系2.3、跳表的复杂度3、Redis中的跳表3.1、zskiplistNode3.2、zskiplist1、简介我们先不谈Redis,来看⼀下跳表。

1.1、业务场景场景来⾃⼩灰的算法之旅,我们需要做⼀个拍卖⾏系统,⽤来查阅和出售游戏中的道具,类似于魔兽世界中的拍卖⾏那样,还有以下需求:拍卖⾏拍卖的商品需要⽀持四种排序⽅式,分别是:按价格、按等级、按剩余时间、按出售者ID排序,排序查询要尽可能地快。

还要⽀持输⼊道具名称的精确查询和不输⼊名称的全量查询。

这样的业务场景所需要的数据结构该如何设计呢?拍卖⾏商品列表是线性的,最容易表达线性结构的是数组和链表。

假如⽤有序数组,虽然查找的时候可以使⽤⼆分法(时间复杂度O(logN)),但是插⼊的时间复杂度是O(N),总体时间复杂度是O(N);⽽如果要使⽤有序链表,虽然插⼊的时间复杂度是O(1),但是查找的时间复杂度是O(N),总体还是O(N)。

那有没有⼀种数据结构,查找时,有⼆分法的效率,插⼊时有链表的简单呢?有的,就是跳表。

1.2、skiplistskiplist,即跳表,⼜称跳跃表,也是⼀种数据结构,⽤于解决算法问题中的查找问题。

⼀般问题中的查找分为两⼤类,⼀种是基于各种平衡术,时间复杂度为O(logN),⼀种是基于哈希表,时间复杂度O(1)。

但是skiplist⽐较特殊,没有在这⾥⾯2、跳表2.1、跳表简介跳表也是链表的⼀种,是在链表的基础上发展出来的,我们都知道,链表的插⼊和删除只需要改动指针就⾏了,时间复杂度是O(1),但是插⼊和删除必然伴随着查找,⽽查找需要从头/尾遍历,时间复杂度为O(N),如下图所⽰是⼀个有序链表(最左侧的灰⾊表⽰⼀个空的头节点)(图⽚来⾃⽹络,以下同):链表中,每个节点都指向下⼀个节点,想要访问下下个节点,必然要经过下个节点,即⽆法跳过节点访问,假设,现在要查找22,我们要先后查找 3->7->11->19->22,需要五次查找。

查找——图文翔解SkipList(跳跃表)

查找——图文翔解SkipList(跳跃表)

查找——图⽂翔解SkipList(跳跃表)跳跃表跳跃列表(也称跳表)是⼀种随机化数据结构,基于并联的链表,其效率可⽐拟于⼆叉查找树(对于⼤多数操作须要O(logn)平均时间)。

基本上。

跳跃列表是对有序的链表添加上附加的前进链接,添加是以随机化的⽅式进⾏的。

所以在列表中的查找能够⾼速的跳过部分列表元素,因此得名。

全部操作都以对数随机化的时间进⾏。

例如以下图所看到的。

是⼀个即为简单的跳跃表。

传统意义的单链表是⼀个线性结构。

向有序的链表中插⼊⼀个节点须要O(n)的时间。

查找操作须要O(n)的时间。

假设我们使⽤图中所看到的的跳跃表。

就能够⼤⼤降低降低查找所需时间。

由于我们能够先通过每⼀个节点的最上层的指针先进⾏查找,这样⼦就能跳过⼤部分的节点。

然后再缩减范围,对以下⼀层的指针进⾏查找,若仍未找到,缩⼩范围继续查找。

上⾯基本上就是跳跃表的思想。

每个结点不单单仅仅包括指向下⼀个结点的指针。

可能包括⾮常多个指向兴许结点的指针,这样就能够跳过⼀些不必要的结点,从⽽加快查找、删除等操作。

对于⼀个链表内每⼀个结点包括多少个指向兴许元素的指针,这个过程是通过⼀个随机函数⽣成器得到。

这样⼦就构成了⼀个跳跃表。

构造由图不难理解跳跃表的原理,能够看出。

跳跃表中的⼀个节点是有不同数⽬的后继指针的。

那么问题来了,这详细是怎样实现的?这些节点是怎样构造的?【分析】我们不可能为每⼀种后继指针数⽬的节点分配⼀种⼤⼩类型的节点,那我们就提取共性,看这些节点有何共通之处。

这些节点可看做由两部分构成:数据域、指针域。

数据域包含key-Value,指针域是后继指针的集合。

那怎样在节点中保存后继指针集合呢?⽤⼀个⼆级指针,分配节点的时候指向动态分配的后继指针数组。

这个⽅法似乎可⾏,但问题在于我们的节点也是动态分配的,这种话,在释放节点的时候还须要先释放节点中动态分配的数组。

释放操作⽐較繁琐。

灵光⼀闪!之前本博客中介绍了⼀种称为“零数组”的技术,或许能够帮到我们。

浅谈“跳跃表”的相关操作及其应用.ppt

浅谈“跳跃表”的相关操作及其应用.ppt

我们利用一个虚拟的“零线”来表示0在数据结构中的相对位置。这样在 进行A和S操作的时候,只要对“零线”进行调整即可,并不需要对所有元 素的值做全面的修改。而在做S操作时,为了维持题意中的性质,要注意将 值低于(“零线”+min)的元素删除。
支持这些操作的数据结构有很多,比如说线段树,伸展树,跳跃表等。
跳跃表的应用
NOI2004 Day1 郁闷的出纳员(Cashier)
I命令时间 复杂度 线段树 伸展树 跳跃表 O(logR) O(logN) O(logN) A命令时间 复杂度 O(1) O(1) O(1) S命令时间 复杂度 O(logR) O(logN) O(logN) F命令时间 复杂度 O(logR) O(logN) O(logN)
基本操作一 查找
目的:在跳跃表中查找一个元素 x
I. II.

在跳跃表中查找一个元素x,按照如下几个步骤进行: 从最上层的链(Sh)的开头开始 假设当前位置为p,它向右指向的节点为q(p与q不一定相邻),且 q的值为y。将y与x作比较
x=y x>y x<y 输出查询成功,输出相关信息 从p向右移动到q的位置 从p向下移动一格
跳跃表的应用
NOI2004 Day1 郁闷的出纳员(Cashier) 抽象题意:
要求维护这样一个数据结构,使得它支持以下四种操作: • I(x) 插入一个值为 x 元素 • A(x) 现有全体元素加上一个值 x • S(x) 现有全体元素减去一个值 x • F(i) 查找现有元素中第 i 大的元素值 (x为105级别) 同时还要保持这样一个性质: 现有的元素必须都大于一个特定的值min,小于min的要删去。
53 53 53 53 53 53

跳跃表——精选推荐

跳跃表——精选推荐

跳跃表跳跃表跳跃表的引⼊⽆论是数组还是链表在插⼊新数据的时候,都会存在性能问题。

排好序的数据,如果使⽤数组,插⼊新数据的⽅式如下:如果要插⼊数据3,⾸先要知道这个数据应该插⼊的位置。

使⽤⼆分查找可以最快定位,这⼀步时间复杂度是O(logN)。

插⼊过程中,原数组中所有⼤于3的商品都要右移,这⼀步时间复杂度是O(N)。

所以总体时间复杂度是O(N)。

如果使⽤链表,插⼊新的数据⽅式如下:如果要插⼊数据3,⾸先要知道这个商品应该插⼊的位置。

链表⽆法使⽤⼆分查找,智能和原链表中的节点逐⼀⽐较⼤⼩来确定位置。

这⼀步的时间复杂度是O(N)。

插⼊的过程倒是很容易,直接改变节点指针的⽬标,时间复杂度O(1)。

因此总体的时间复杂度也是O(N)。

跳跃表原理基本概念如果对于有⼏⼗万的数据集合来说,这两种⽅法显然都太慢了。

为此可以引⼊跳跃表(skiplist),跳跃表是⼀种基于有序链表的扩展,简称跳表。

利⽤类似索引的思想,提取出链表中的部分关键节点。

⽐如给定⼀个长度是7的有序链表,节点值依次是1->2->3->4->5->6->7->8。

那么我们可以取出所有值为奇数的节点作为关键点。

此时如果插⼊⼀个值是4的新节点,不再需要和原节点8,7,6,5,3逐⼀⽐较,只需要⽐较关键节点7,5,3。

确定了新节点在关键点中的位置(3和5之间),就可以回到原链表,迅速定位到对应的位置插⼊(同样是3和5之间)。

现在节点数⽬少,优化效果还不明显,如果链表中1w甚⾄10w个节点,⽐较次数就整整减少了⼀半。

这样做虽然增加50%的额外的空间,但是性能提⾼了⼀倍。

不过可以进⼀步思考,既然已经提取出了⼀层关键节点作为索引,那我们可以从索引中进⼀步提取,提出⼀层索引的索引。

当节点⾜够多的时候,我们不⽌能提出两层索引,还可以向更⾼层次提取,保证每⼀层是上⼀层节点数的⼀半。

⾄于提取的极限,则是同⼀层只有两个节点的时候,因为⼀个节点没有⽐较的意义。

算法合集之浅谈“跳跃表”的相关操作及其应用

算法合集之浅谈“跳跃表”的相关操作及其应用

让算法的效率“跳起来”!——浅谈“跳跃表”的相关操作及其应用上海市华东师范大学第二附属中学魏冉【目录】◆关键字 (2)◆摘要 (2)◆概述及结构 (2)◆基本操作 (3)◇查找 (3)◇插入 (3)◇删除 (4)◇“记忆化”查找 (5)◆复杂度分析 (6)◇空间复杂度分析 (7)◇跳跃表高度分析 (7)◇查找的时间复杂度分析 (7)◇插入与删除的时间复杂度分析 (8)◇实际测试效果 (8)◆跳跃表的应用 (9)◆总结 (10)◆附录 (11)【关键字】跳跃表 高效 概率 随机化【摘要】本文分为三大部分。

首先是概述部分。

它会从功能、效率等方面对跳跃表作一个初步的介绍,并给出其图形结构,以便读者对跳跃表有个形象的认识。

第二部分将介绍跳跃表的三种基本操作——查找,插入和删除,并对它们的时空复杂度进行分析。

第三部分是对跳跃表应用的介绍,并通过实际测试效果来对跳跃表以及其它一些相关数据结构进行对比,体现其各自的优缺点。

最后一部分是对跳跃表数据结构的总结。

【概述及结构】二叉树是我们都非常熟悉的一种数据结构。

它支持包括查找、插入、删除等一系列的操作。

但它有一个致命的弱点,就是当数据的随机性不够时,会导致其树型结构的不平衡,从而直接影响到算法的效率。

跳跃表(Skip List )是1987年才诞生的一种崭新的数据结构,它在进行查找、插入、删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领。

而且最重要的一点,就是它的编程复杂度较同类的AVL 树,红黑树等要低得多,这使得其无论是在理解还是在推广性上,都有着十分明显的优势。

首先,我们来看一下跳跃表的结构(如图1)53 53 5345 45373030 30 29151111 11 11 -∞-∞ -∞ -∞ +∞+∞ +∞ +∞ 图1 有7个元素的跳跃表S 0 S 1 S 2 S 3跳跃表由多条链构成(S 0,S 1,S 2 ……,S h ),且满足如下三个条件:(1) 每条链必须包含两个特殊元素:+∞ 和 -∞(2) S 0包含所有的元素,并且所有链中的元素按照升序排列。

跳表SkipList

跳表SkipList

跳表SkipList1.聊一聊跳表作者的其人其事2. 言归正传,跳表简介3. 跳表数据存储模型4. 跳表的代码实现分析5. 论文,代码下载及参考资料<1>. 聊一聊作者的其人其事跳表是由William Pugh发明。

他在Communications of the ACM June 1990, 33(6) 668-676 发表了Skip lists: a probabilistic alternative to balanced trees,在该论文中详细解释了跳表的数据结构和插入删除操作。

William Pugh同时还是FindBug(没有使用过,这是一款java 的静态代码分析工具,直接对java 的字节码进行分析,能够找出java 字节码中潜在很多错误。

)作者之一。

现在是University of Maryland, College Park(马里兰大学伯克分校,位于马里兰州,全美大学排名在五六十名左右的样子)大学的一名教授。

他和他的学生所作的研究深入的影响了java语言中内存池实现。

又是一个计算机的天才!<2>. 言归正传,跳表简介这是跳表的作者,上面介绍的William Pugh给出的解释:Skip lists are a data structure that canbe used in place of balanced trees. Skiplists use probabilistic balancing ratherthan strictly enforced balancing and as aresult the algorithms for insertion anddeletion in skip lists are much simpler andsignificantly faster than equivalentalgorithms for balanced trees.跳表是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。

浅谈跳跃表的相关操作及其应用

浅谈跳跃表的相关操作及其应用

总结点数 次数(平均值) 次数(平均值) 次数(平均值)
2/3
0.0024690
3.004
ms
91233
39.878
41.604
41.566
1/2
0.0020180
1.995
ms
60683
27.807
29.947
29.072
1/e
0.0019870
1.584
ms
47570
27.332
28.238
28.452
“跳跃表” — 新生的宠儿
• 跳跃表(Skip List)是1987年才诞生的一种崭 新的数据结构,它在进行查找、插入、删除 等操作时的时间复杂度均为O(logn),有着近 乎替代平衡树的本领。而且最重要的一点, 就是它的编程复杂度较同类的AVL树,红黑 树等要低得多,这使得其无论是在理解还是 在推广性上,都有着十分明显的优势。
(期望) (期望)
(期望) (期望) (期望)
基本操作一 查找
目的:在跳跃表中查找一个元素 x
在跳跃表中查找一个元素x,按照如下几个步骤进行:
I.
从最上层的链(Sh)的开头开始
II. 假设当前位置为p,它向右指向的节点为q(p与q不一定相邻),且
q的值为y。将y与x作比较
x=y
输出查询成功,输出相关信息
跳跃表的应用
• NOI2004 Day1 郁闷的出纳员(Cashier)
线段 伸展树 跳跃表
I命令时间 复杂度 O(logR) O(logN) O(logN)
A命令时间 复杂度 O(1) O(1) O(1)
S命令时间 复杂度 O(logR) O(logN) O(logN)

Go语言的跳跃表(SkipList)实现

Go语言的跳跃表(SkipList)实现

Go语⾔的跳跃表(SkipList)实现之所以会有这篇⽂章,是因为我在学习Go语⾔跳表代码实现的过程中,产⽣过⼀些困惑,但⽹上的⼤家都不喜欢写注释- -我的代码注释⼀向是写的很全的,所以发出来供后来者学习参考。

本⽂假设你已经理解了跳表的原理,不再做细节阐述。

(可能会考虑以后补充)代码实现参考了但以上项⽬实现是有问题的(截⾄2020/06/14 14:18),Find ⽅法有概率查不到 score 重复的不同元素,Delete ⽅法也有bug,有可能会出现空指针,代码写的略奇怪,我直接重写了。

我还没去提pull request,因为测试⽤例我完全没写。

有空补充⼀下。

本⽂的跳跃表参考了Redis跳表实现,加⼊了score属性区分优先级,跳表元素不能重复,允许score重复。

定义数据结构如下:type SkipList struct {Head *SkipListNodeLevelN int // 包含原始链表的层数Length int // 长度}type SkipListNode struct {Val interface{}Level int // 该节点的最⾼索引层是多少Score intNext []*SkipListNode // key是层⾼}提供了以下⽅法:func (sl *SkipList) Insert(val interface{}, score int) (bool, error)func (sl *SkipList) Find(v interface{}, score int) *skipListNodefunc (sl *SkipList) Delete(v interface{}, score int) bool此外,可以使⽤func (sl *SkipList) printSkipList()⽅法打印跳跃表的索引结构,加深理解。

整体代码如下:const MAX_LEVEL = 16 // 最⼤索引层级限制// 不⽀持重复元素// ⽀持相同的scoretype SkipList struct {Head *skipListNodeLevelN int // 包含原始链表的层数Length int // 长度}type skipListNode struct {Val interface{}Level int // 该节点的最⾼索引层是多少Score intNext []*skipListNode // key是层⾼}func NewSkipList() *SkipList {return &SkipList{Head: newSkipListNode(nil, math.MinInt64, MAX_LEVEL),LevelN: 1,Length: 0,}}func newSkipListNode(val interface{}, score, level int) *skipListNode {return &skipListNode{Val: val,Level: level,Score: score,Next: make([]*skipListNode, level, level),}}func (sl *SkipList) Insert(val interface{}, score int) (bool, error) {if val == nil {return false, errors.New("can't insert nil value to skiplist")}cur := sl.Headupdate := [MAX_LEVEL]*skipListNode{} // 记录在每⼀层的插⼊位置,value保存哨兵结点k := MAX_LEVEL - 1// 从最⾼层的索引开始查找插⼊位置,逐级向下⽐较,最后插⼊到原始链表也就是第0级for ; k >= 0; k-- {for cur.Next[k] != nil {if cur.Next[k].Val == val {return false, errors.New("can't insert repeatable value to skiplist")}if cur.Next[k].Score > score {update[k] = curbreak}cur = cur.Next[k]}// 如果待插⼊元素的优先级最⼤,哨兵节点就是最后⼀个元素if cur.Next[k] == nil {update[k] = cur}}randomLevel := sl.getRandomLevel()newNode := newSkipListNode(val, score, randomLevel)// 插⼊元素for i := randomLevel - 1; i >= 0; i-- {newNode.Next[i] = update[i].Next[i]update[i].Next[i] = newNode}if randomLevel > sl.LevelN {sl.LevelN = randomLevel}sl.Length++return true, nil}// skiplist在插⼊元素时需要维护索引,⽣成⼀个随机值,将元素插⼊到第1-k级索引中func (sl *SkipList) getRandomLevel() int {level := 1for i := 1; i < MAX_LEVEL; i++ {if rand.Int31()%7 == 1 {level++}}return level}func (sl *SkipList) Find(v interface{}, score int) *skipListNode {if v == nil || sl.Length == 0 {return nil}cur := sl.Headfor i := sl.LevelN - 1; i >= 0; i-- {if cur.Next[i] != nil {if cur.Next[i].Val == v && cur.Next[i].Score == score {return cur.Next[i]} else if cur.Next[i].Score >= score {continue}cur = cur.Next[i]}}// 如果没有找到该元素,这时cur是原始链表中,score相同的第⼀个元素,向后查找for cur.Next[0].Score <= score {if cur.Next[0].Val == v && cur.Next[0].Score == score {return cur.Next[0]}cur = cur.Next[0]}return nil}func (sl *SkipList) Delete(v interface{}, score int) bool {if v == nil {return false}cur := sl.Head// 记录每⼀层待删除数据的前驱结点// 如果某些层没有待删除数据,那么update[i]为空// 如果待删除数据不存在,那么update[i]也为空update := [MAX_LEVEL]*skipListNode{}for i := sl.LevelN - 1; i >= 0; i-- {for cur.Next[i] != nil && cur.Next[i].Score <= score {if cur.Next[i].Score == score && cur.Next[i].Val == v {update[i] = curbreak}cur = cur.Next[i]}}// 删除节点for i := sl.LevelN - 1; i >= 0; i-- {if update[i] == nil {continue}// 如果该层中,删除节点是第⼀个节点且没有下⼀个节点,直接降低索引层(只有最⾼层会出现这种情况) if update[i] == sl.Head && update[i].Next[i].Next[i] == nil {sl.LevelN = icontinue}update[i].Next[i] = update[i].Next[i].Next[i]}sl.Length--return true}func (sl *SkipList) printSkipList() {if sl.Length > 0 {for i := 0; i < sl.LevelN; i++ {cur := sl.Headoutput := fmt.Sprintf("The %dth skipList is: ", i)for cur.Next[i] != nil && cur.Next[i].Val != nil {// value(score)output += fmt.Sprintf("-%v(%d)-", cur.Next[i].Val, cur.Next[i].Score) cur = cur.Next[i]}fmt.Println(output)}}}之后有空可能会补充⼀些API,不过最关键的就是这些了。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

S3 -
+ p2
S2 -
34
+
p1
S1 -
23 34
+
p0
S0 - 12 23 34 45 +
2/6/2020
Skip Lists
S2 -
+
S1 -
23
+
S0 - 12 23 45 +
7
Implementation
We can implement a skip list with quad-nodes
At the current position p, we compare x with y key(after(p))
x = y: we return element(after(p)) x > y: we “scan forward” x < y: we “drop down” If we try to drop down past the bottom list, we return NO_SUCH_KEY
The drop-down steps are bounded by the height of the skip list and thus are O(log n) with high probability
To analyze the scan-forward steps, we use yet another probabilistic fact:
Example: search for 78
S3 -
S2 -
S1 -
23
S0 -
12
23
26
+
31
+
31 34
64
+
31
34
44
56
64
78
+
2/6/2020
Skip Lists
4
Randomized Algorithms
A randomized algorithm performs coin tosses (i.e., uses random bits) to control its execution
Consider a skip list with n items
By Fact 1, we insert an item in list Si with probability 1/2i
By Fact 3, the probability that list Si has at least one item is at most n/2i
The running time of the search an insertion algorithms is affected by the height h of the skip list
We show that with high probability, a skip list with n items has height O(log n)
2/6/2020
Skip Lists
10
Search and Update Times
The search time in a skip list is proportional to
the number of drop-down steps, plus
the number of scan-forward steps
S0 S1 … Sh List Sh contains only the two special keys
We show how to use a skip list to implement the dictionary ADT
S3 -
S2 -
S1 -
23
S0 -
12
23
26
We use the following additional probabilistic fact:
Fact 3: If each of n events has probability p, the probability that at least one event occurs is at most np
Implementation Analysis (§3.5.3)
Space usage Search and update times
Comparison of dictionary implementations
2/6/2020
Skip Lists
2
What is a Skip List
It contains statements of the type
b random()
if b = 0
do A …
else { b = 1}
do B …
Its running time depends on the outcomes of the coin tosses
We analyze the expected running time of a randomized algorithm under the following assumptions
We search for x in the skip list and find the positions p0, p1 , …, pi of the items with largest key less than x in each list S0, S1, … , Si
For j 0, …, i, we insert item (x, o) into list Sj after position pj
The expected number of nodes used by the skip list is
h
i=0
n 2i
h
=n
i=0
1 2i
<2n
Thus, the expected space usage of a skip list with n items is O(n)
9
Height
We repeatedly toss a coin until we get tails, and we denote with i the number of times the coin came up heads
If i h, we add to the skip list new lists Sh+1, … , Si +1, each containing only the two special keys
We use a randomized algorithm to insert items into a skip list
2/6/2020
Skip Lists
5
Insertion
To insert an item (x, o) into a skip list, we use a randomized algorithm:
By picking i = 3log n, we have that the probability that S3log n has at least one item is at most
n/23log n = n/n3 = 1/n2
Thus a skip list with n items has height at most 3log n with probability at least 1 - 1/n2
To remove an item with Байду номын сангаасey x from a skip list, we proceed as follows:
We search for x in the skip list and find the positions p0, p1 , …, pi of the items with key x, where position pj is in list Sj
We remove positions p0, p1 , …, pi from the lists S0, S1, … , Si We remove all but one list containing only the two special keys
Example: remove key 34
A skip list for a set S of distinct (key, element) items is a series of lists S0, S1 , … , Sh such that
Each list Si contains the special keys + and - List S0 contains the keys of S in nondecreasing order Each list is a subsequence of the previous one, i.e.,
the coins are unbiased, and
the coin tosses are independent
The worst-case running time of a randomized algorithm is often large but has very low probability (e.g., it occurs when all the coin tosses give “heads”)
2/6/2020
Skip Lists
Consider a skip list with n items
By Fact 1, we insert an item in list Si with probability 1/2i
相关文档
最新文档