第03章跳跃表SkipLists-精品
详解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(跳跃表)跳跃表跳跃列表(也称跳表)是⼀种随机化数据结构,基于并联的链表,其效率可⽐拟于⼆叉查找树(对于⼤多数操作须要O(logn)平均时间)。
基本上。
跳跃列表是对有序的链表添加上附加的前进链接,添加是以随机化的⽅式进⾏的。
所以在列表中的查找能够⾼速的跳过部分列表元素,因此得名。
全部操作都以对数随机化的时间进⾏。
例如以下图所看到的。
是⼀个即为简单的跳跃表。
传统意义的单链表是⼀个线性结构。
向有序的链表中插⼊⼀个节点须要O(n)的时间。
查找操作须要O(n)的时间。
假设我们使⽤图中所看到的的跳跃表。
就能够⼤⼤降低降低查找所需时间。
由于我们能够先通过每⼀个节点的最上层的指针先进⾏查找,这样⼦就能跳过⼤部分的节点。
然后再缩减范围,对以下⼀层的指针进⾏查找,若仍未找到,缩⼩范围继续查找。
上⾯基本上就是跳跃表的思想。
每个结点不单单仅仅包括指向下⼀个结点的指针。
可能包括⾮常多个指向兴许结点的指针,这样就能够跳过⼀些不必要的结点,从⽽加快查找、删除等操作。
对于⼀个链表内每⼀个结点包括多少个指向兴许元素的指针,这个过程是通过⼀个随机函数⽣成器得到。
这样⼦就构成了⼀个跳跃表。
构造由图不难理解跳跃表的原理,能够看出。
跳跃表中的⼀个节点是有不同数⽬的后继指针的。
那么问题来了,这详细是怎样实现的?这些节点是怎样构造的?【分析】我们不可能为每⼀种后继指针数⽬的节点分配⼀种⼤⼩类型的节点,那我们就提取共性,看这些节点有何共通之处。
这些节点可看做由两部分构成:数据域、指针域。
数据域包含key-Value,指针域是后继指针的集合。
那怎样在节点中保存后继指针集合呢?⽤⼀个⼆级指针,分配节点的时候指向动态分配的后继指针数组。
这个⽅法似乎可⾏,但问题在于我们的节点也是动态分配的,这种话,在释放节点的时候还须要先释放节点中动态分配的数组。
释放操作⽐較繁琐。
灵光⼀闪!之前本博客中介绍了⼀种称为“零数组”的技术,或许能够帮到我们。
浅谈“跳跃表”的相关操作及其应用.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
跳表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语⾔跳表代码实现的过程中,产⽣过⼀些困惑,但⽹上的⼤家都不喜欢写注释- -我的代码注释⼀向是写的很全的,所以发出来供后来者学习参考。
本⽂假设你已经理解了跳表的原理,不再做细节阐述。
(可能会考虑以后补充)代码实现参考了但以上项⽬实现是有问题的(截⾄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,不过最关键的就是这些了。
数据结构与算法(c++)——跳跃表(skiplist)
数据结构与算法(c++)——跳跃表(skiplist)今天要介绍⼀个这样的数据结构:1. 单向链接2. 有序保存3. ⽀持添加、删除和检索操作4. 链表的元素查询接*线性时间——跳跃表 Skip List⼀、普通链表对于普通链接来说,越靠前的节点检索的时间花费越低,反之则越⾼。
⽽且,即使我们引⼊复杂算法,其检索的时间花费依然为O(n)。
为了解决长链表结构的检索问题,⼀位名叫William Pugh的⼈于1990年提出了跳跃表结构。
基本思想是——以空间换时间。
⼆、简单跳跃表(Integer结构)跳跃表的结构是多层的,通过从最⾼维度的表进⾏检索再逐渐降低维度从⽽达到对任何元素的检索接*线性时间的⽬的O(logn)。
如图:对节点8的检索⾛红⾊标记的路线,需要4步。
对节点5的检索⾛蓝⾊路线,需要4步。
由此可见,跳跃表本质上是⼀种⽹络布局结构,通过增加检索的维度(层数)来减少链表检索中需要经过的节点数。
理想跳跃表应该具备如下特点:包含有N个元素节点的跳跃表拥有log2N层,并且上层链表包含的节点数恰好等于下层链表节点数的1/2。
但如此严苛的要求在算法上过于复杂。
因此通常的做法是:每次向跳跃表中增加⼀个节点就有50%的随机概率向上层链表增加⼀个跳跃节点,并以此类推。
接下来,我们做如下规范说明:1. 跳跃表的层数,我们称其维度。
⾃顶向下,我们称为降维,反之亦然。
2. 表中,处于不同链表层的相同元素。
我们称为“同位素”。
3. 最底层的链表,即包含了所有元素节点的链表是L1层,或称基础层。
除此以外的所有链表层都称为跳跃层。
以下是代码实现#pragma once#ifndef SKIPLIST_INT_H_#define SKIPLIST_INT_H_#include <cstdlib> /* srand, rand */#include <ctime> /* time */#include <climits> /* INT_MIN *//* 简单跳跃表,它允许简单的插⼊和删除元素,并提供O(logn)的查询时间复杂度。
redis源码分析之数据结构:跳跃表
redis源码分析之数据结构:跳跃表跳跃表是⼀种随机化的,在查找、插⼊和删除这些字典操作上,其效率可⽐拟于平衡⼆叉树(如红⿊树),⼤多数操作只需要O(log n)平均时间,但它的代码以及原理更简单。
和链表、字典等数据结构被⼴泛地应⽤在Redis内部不同,Redis只在两个地⽅⽤到了跳跃表,⼀个是实现有序集合键,另⼀个是在集群结点中⽤作内部数据结构。
除此之外,跳跃表在Redis⾥⾯没有其他⽤途。
/* ZSETs use a specialized version of Skiplists */typedef struct zskiplistNode {robj *obj;double score;struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned int span;//代表该节点在每层到下⼀个节点所跨越的节点长度} level[];} zskiplistNode;typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;} zskiplist;obj是该结点的成员对象指针,score是该对象的分值,是⼀个浮点数,跳跃表中的所有结点,都是根据score从⼩到⼤来排序的。
同⼀个跳跃表中,各个结点保存的成员对象必须是唯⼀的,但是多个结点保存的分值却可以是相同的:分值相同的结点将按照成员对象的字典顺序从⼩到⼤进⾏排序。
level数组是⼀个柔性数组成员,它可以包含多个元素,每个元素都包含⼀个层指针(level[i].forward),指向该结点在本层的后继结点。
该指针⽤于从表头向表尾⽅向访问结点。
可以通过这些层指针来加快访问结点的速度。
每次创建⼀个新跳跃表结点的时候,程序都根据幂次定律(power law,越⼤的数出现的概率越⼩)随机⽣成⼀个介于1和32之间的值作为level数组的⼤⼩,这个⼤⼩就是该结点包含的层数。
跳表和散列 - 3
并输出其相应的代码。 若输入文件中下一个字符为 c,则为pc分配下一个代码,并插入 字典。
8/16/2015 5
7.5 文本压缩
LZW( Lempel、Ziv、Welch)压缩
8/16/2015
4
7.5 LZW方法
Abraham Lempel , Jacob Ziv 和 Terry Welch 发 明的基于表查寻算法把文件压缩成小文件的压缩 方法,称为LZW压缩方法。 LZW 算法包含着代码查询表并把它作为压缩文件 的一部分。解压缩的解码程序可以像加密输入的 时候那样通过使用算法自己建立这个表。
text(p)=text(q)fc(q), 代码串:qp 对应文本串:text(q)text(q)fc(q)
9
LZW解压缩示例
代码0214537。0->a,1->b 首先,初始化字典,在其中插入(0,a)和(1,b)。 压缩文件的第一个代码为0,则应用a代替。 下一个代码2未定义。因为前一个代码为0,且 text(0)=a,fc(0)=a , 则 text(2)=text(0)fc(0)=aa 。 因此用aa代替2,并把(2,aa)插入字典中。
在不压缩时占用的空间为3002字节(每个x或y占用一个字节,2个 字节用来表示串的结尾)。 采 用 游 程 长 度 编 码 (run-length-coding) , 可 存 储 为 字 符 串 1000x2000y,仅为10个字母,占用12个字节。 当要读取编码文件时,需对其进行解码。由压缩器(compressor) 对文件进行编码,由解压器(decompressor)进行解码。
[Redis]Redis的设计与实现-链表字典跳跃表
[Redis]Redis的设计与实现-链表字典跳跃表redis的设计与实现:1.假如有⼀个⽤户关系模块,要实现⼀个共同关注功能,计算出两个⽤户关注了哪些相同的⽤户,本质上是计算两个⽤户关注集合的交集,如果使⽤关系数据库,需要对两个数据表执⾏join操作,对合并的结果执⾏去重distinct操作,⾮常复杂2.Redis直接内置了集合数据类型,⽀持对集合执⾏交集/并集/差集等集合计算操作,交集操作可以直接⽤于共同关注功能,使⽤之后速度更快代码量更少,可读性⼤⼤提⾼3.越来越多的疑问:五种数据类型是由什么数据结构实现的?字符串数据类型既可以存储字符串,⼜可以存储整数浮点数,⼆进制位,在内部是怎么存储这些值的?有些命令只能对特定数据类型执⾏,是如何进⾏类型检查的?怎样存储各种不同类型的键值对?过期键是怎样实现⾃动删除的?发布与订阅/脚本/事务等特性是如何实现的?使⽤什么模型处理客户端的命令请求?⼀条命令从发送到返回需要经历的步骤?4.第⼀版发布的时候还不是很完善,作者⼀边注释源码⼀边写,只介绍了内部机制和单机特性,新版添加了关于⼆进制位操作/排序/复制/Sentinel 和集群等主题的新章节5.数据结构与对象,单机数据库的实现,多机数据库的实现,独⽴功能的实现6.数据库⾥⾯的每个键值对都是由对象组成的:数据库键总是字符串对象;键的值可以是字符串对象/列表对象(list object)/哈希对象(hash object)/集合对象(set object)/有序集合对象(sorted set object),这五种中的其中⼀种7.第⼀部分和第⼆部分单机功能⽐较重要:第⼀部分,简单动态字符串,链表,字典,跳跃表,整数集合,压缩列表,对象8.Redis⾃⼰构建了⼀个SDS的类型⽤来保存所有的字符串对象,包括键值对的键,值中存储字符串对象的底层也是SDSredis的设计与实现-链表1.链表提供了⾼效的节点重排能⼒,顺序性的节点访问⽅式,通过增删节点调整链表的长度,C语⾔不内置,Redis构建了⾃⼰的链表实现2.列表键的底层实现之⼀就是链表,当元素⽐较多,元素都是⽐较长的字符串,就会使⽤链表作为底层实现3.发布与订阅,慢查询,监视器等功能也⽤到了链表,redis本⾝使⽤链表保存多个客户端的状态信息4.每个链表节点使⽤adlist.h/listNode结构表⽰,通过prev和next指针组成双端链表;使⽤adlist.h/list结构操作更⽅便,提供了表头指针head,表尾指针tail,长度计数len,特定类型的函数等5.链表表头前置和表尾后置都是指向null,所以是⽆环链表,设置不同类型特定函数,可以⽤于保存不同类型的值字典1.字典,⼜称为符号表/关联数组/映射,保存键值对的抽象数据结构;⼀个键和⼀个值进⾏关联,或者叫键映射为值2.redis的数据库就是使⽤字典作为底层,对数据库的增删查改操作也是构建在对字典的操作之上;字典还是哈希键的底层实现3.redis的字典使⽤哈希表作为底层实现,⼀个哈希表⾥⾯可以有多个哈希表节点,每个哈希表节点保存了字典中的⼀个键值对4.redis字典所使⽤的哈希表由dict.h/dictht结构,table属性是⼀个数组,每个元素都是指向dict.h/dictEntry结构的指针.每个dictEntry结构保存⼀个键值对5.哈希表节点使⽤dictEntry结构表⽰,key属性保存着键值对中的键,v属性保存着键值对中的值,键值对的值可以是指针或整数,next属性是指向另⼀个哈希表节点的指针,以此解决键冲突,通过next指针将两个索引值相同的键k1和k0连接在⼀起6.Redis字典由dict.h/dict结构表⽰,type属性和privdata属性是针对不同类型的键值对,为创建多态字典设置;ht属性是⼀个包含两个项的数组,每⼀项都是dictht哈希表,⼀般只使⽤ht[0],ht[1]只会在哈希表进⾏rehash的时候使⽤,rehashidx记录rehash的进度7.哈希算法-将⼀个新的键值对添加到字典⾥⾯时,先根据键计算出哈希值和索引值,根据索引值将⼀个新键值对的哈希表节点放到哈希表数组的指定索引上hash=dict->type->hashFunction(key);index=hash&dict->ht[x].sizemaskRedis使⽤了MurmurHash2算法来计算键的哈希值8.解决键冲突,使⽤了链地址法,被分配到同⼀个索引的多个节点可以⽤单向链表连接起来9.哈希表保存的键值对逐渐增多或者减少,为了让哈希表的负载因⼦维持在⼀个合理的范围内,程序对⼤⼩进⾏扩展或者收缩redis的设计与实现-跳跃表1.跳跃表(skiplist)是⼀种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,达到快速访问其他节点的⽬的,跳跃表⽀持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点1.跳跃表(skiplist)⼤部分情况下效率可以和平衡树媲美,并且⽐平衡树要简单2.Redis使⽤跳跃表作为有序集合键的底层实现之⼀,在内部的集群节点中也有使⽤3.⽐如zrange fruit 0 2 withscores ⽔果名是成员,⽔果价钱是分数值,每个⽔果存储在跳跃表节点中,价钱由低到⾼排序4.redis跳跃表由redis.h/zskiplist和redis.h/zskiplistNode两个结构定义,zskiplist包含,header指向表头节点,tail指向表尾节点,length跳跃表的长度,level节点中最⾼层数zskiplistNode结构包含,level表⽰层每层都有前进指针和跨度指向下⼀个节点,backward表⽰后退指针,score表⽰分值,obj表⽰成员对象;遍历时这些前进指针和后退指针就能启动快速访问的⽬的5.迭代程序遍历跳跃表的时候只与前进指针有关,每个层的跨度与节点在跳跃表中的排位有关,每个节点的层⾼在1-32之间的随机数。
redis源码分析4---结构体---跳跃表
redis源码分析4---结构体---跳跃表redis源码分析4---结构体---跳跃表跳跃表是⼀种有序的数据结构,他通过在每个节点中维持多个指向其他节点的指针,从⽽达到快速访问节点的⽬的;跳跃表⽀持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
性能上和平衡树媲美,因为事先简单,常⽤来代替平衡树。
在redis中,只在两个地⽅使⽤了跳跃表,⼀个是实现有序集合键,另⼀个是在集群节点中⽤作内部数据结构。
1 跳跃表节点1.1 层层的数量越多,访问其他节点的速度越快;1.2 前进指针遍历举例步骤1.3 跨度跨度记录两个节点之间的距离1.4 后退指针⽤于从表尾向表头放⼼访问节点;1.5 分值和成员2 跳跃表结构3 跳跃表API4 源代码分析源代码中,结构体定义在redis.h中,实现在t_zset.c4.1 建⽴和释放/** 创建⼀个层数为 level 的跳跃表节点,* 并将节点的成员对象设置为 obj ,分值设置为 score 。
** 返回值为新创建的跳跃表节点** T = O(1)*/zskiplistNode *zslCreateNode(int level, double score, robj *obj) {// 分配空间zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel)); // 设置属性zn->score = score;zn->obj = obj;return zn;}/** 创建并返回⼀个新的跳跃表** T = O(1)*/zskiplist *zslCreate(void) {int j;zskiplist *zsl;// 分配空间zsl = zmalloc(sizeof(*zsl));// 设置⾼度和起始层数zsl->level = 1;zsl->length = 0;// 初始化表头节点// T = O(1)zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {zsl->header->level[j].forward = NULL;zsl->header->level[j].span = 0;}zsl->header->backward = NULL;// 设置表尾zsl->tail = NULL;return zsl;}/** 释放给定的跳跃表节点** T = O(1)*/void zslFreeNode(zskiplistNode *node) {decrRefCount(node->obj);zfree(node);}/** 释放给定跳跃表,以及表中的所有节点** T = O(N)*/void zslFree(zskiplist *zsl) {zskiplistNode *node = zsl->header->level[0].forward, *next;// 释放表头zfree(zsl->header);// 释放表中所有节点// T = O(N)while(node) {next = node->level[0].forward;zslFreeNode(node);node = next;}// 释放跳跃表结构zfree(zsl);}/* Returns a random level for the new skiplist node we are going to create.** 返回⼀个随机值,⽤作新跳跃表节点的层数。
跳跃表(SkipList)
跳跃表(SkipList)跳跃表(Skip List)是1987年才诞⽣的⼀种崭新的数据结构,它在进⾏查找、插⼊、删除等操作时的时间复杂度均为O(logn),有着近乎替代平衡树的本领。
⽽且最重要的⼀点,就是它的编程复杂度较同类的AVL树,红⿊树等要低得多,这使得其⽆论是在理解还是在推⼴性上,都有着⼗分明显的优势。
跳跃表由多条链构成(S0,S1,S2 ……,Sh),且满⾜如下三个条件:每条链必须包含两个特殊元素:+∞ 和 -∞。
S0包含所有的元素,并且所有链中的元素按照升序排列。
每条链中的元素集合必须包含于序数较⼩的链的元素集合,即:1.插⼊(时间复杂度是O(logn))在跳跃表中插⼊⼀个元素 x插⼊操作由两部分组成:查找插⼊的位置和插⼊对应元素。
为了确定插⼊的“列⾼”,我们引⼊⼀个随机决策模块:产⽣⼀个0到1的随机数r,如果r⼩于⼀个概率因⼦P,则执⾏A操作,否则,执⾏B操作:A操作,则将列的⾼度加1,并且继续反复执⾏随机决策模块。
B操作,结束决策,并向跳跃表中插⼊⼀个⾼度为i的列。
在插⼊操作中,我们引⼊了⼀个概率因⼦P,它决定了跳跃表的⾼度,并影响到了整个数据结构的效率。
2.查找(时间复杂度是O(logn))在跳跃表中查找⼀个元素x,按照如下⼏个步骤进⾏:从最上层的链(Sh)的开头开始假设当前位置为p,它向右指向的节点为q(p与q不⼀定相邻),且q的值为y。
将y与x作⽐较:如果x=y,输出查询成功,输出相关信息;如果x>y,从p向右移动到q的位置;如果x<y,从p向下移动⼀格,如果当前位置在最底层的链S0中,且还要往下移动的话,则输出查询失败。
3.删除(时间复杂度是O(logn))删除操作分为以下三个步骤:在跳跃表中查找到这个元素的位置,如果未找到,则退出;将该元素所在整列从表中删除;将多余的“空链”删除。
Redis源码解析之跳跃表(一)
Redis源码解析之跳跃表(⼀)跳跃表(skiplist)有序集合(sorted set)是Redis中较为重要的⼀种数据结构,从名字上来看,我们可以知道它相⽐⼀般的集合多了⼀个有序。
Redis的有序集合会要求我们给定⼀个分值(score)和元素(element),有序集合将根据我们给定的分值对元素进⾏排序。
Redis共有两种编码来实现有序集合,⼀种是压缩列表(ziplist),另⼀种是跳跃表(skiplist),也是本章的主⾓。
下⾯,让笔者带领⼤家稍微了解下有序集合的使⽤。
假设某软件公司统计了公司内的程序员所掌握的编程语⾔,掌握Java的⼈数有90⼈、掌握C的⼈数有20⼈、掌握Python的⼈数有57⼈、掌握Go的⼈数有82⼈、掌握PHP的⼈数有61⼈、掌握Scala的⼈数有28⼈、掌握C++的⼈数有33⼈。
我们⽤key为worker-language的有序集合来存储这⼀结果。
127.0.0.1:6379> ZADD worker-language 90 Java(integer) 1127.0.0.1:6379> ZADD worker-language 20 C(integer) 1127.0.0.1:6379> ZADD worker-language 57 Python(integer) 1127.0.0.1:6379> ZADD worker-language 82 Go(integer) 1127.0.0.1:6379> ZADD worker-language 61 PHP(integer) 1127.0.0.1:6379> ZADD worker-language 28 Scala(integer) 1127.0.0.1:6379> ZADD worker-language 33 C++(integer) 1将上⾯的统计结果形成⼀个有序集合后,我们可以对有序集合进⾏⼀些业务上的操作,⽐如⽤:ZCARD key返回集合的长度:127.0.0.1:6379> ZCARD worker-language(integer) 7可以把集合当成⼀个数组,使⽤ZRANGE key start stop [WITHSCORES]命令指定索引区间返回区间内的成员,⽐如我们指定start为0,stop 为-1,则会返回从索引0到集合末尾所有的元素,即[0,6],如果有序集合⾥⾯有10个元素,则[0,-1]也代表[0,9],带上WITHSCORES选项,除了返回元素本⾝,也会返回元素的分值。
concurrentskiplistset 的顺序 -回复
concurrentskiplistset 的顺序-回复这是一个关于"ConcurrentSkipListSet的顺序"的主题,ConcurrentSkipListSet是Java集合框架中的一种有序、线程安全的集合,本文将一步一步回答这个问题。
首先,我们需要了解什么是ConcurrentSkipListSet。
ConcurrentSkipListSet是Java并发集合框架中的一个类,它基于跳表(SkipList)数据结构实现。
跳表是一种允许高效查找、插入和删除的数据结构,特别适用于有序元素的存储和操作。
跳表的结构是多层链表,每一层都是一个有序链表。
顶层链表包含所有元素,而底层链表包含部分元素。
每一层链表都是按升序排列的。
每个节点都包含一个元素以及指向下一层节点和下一个节点的指针。
ConcurrentSkipListSet提供了一些特性。
首先,它是有序的,这意味着元素将按升序排序。
其次,它是线程安全的,这意味着多个线程可以同时访问和修改集合。
第三,它允许高效的插入、删除和查找操作,这是跳表的特点之一。
ConcurrentSkipListSet的顺序是根据元素的自然排序或比较器来确定的。
当我们将元素添加到集合中时,集合将根据元素的顺序将其插入到相应的位置上。
元素将按升序排列,这是ConcurrentSkipListSet的一个重要特性。
对于自然排序,ConcurrentSkipListSet要求元素实现Comparable接口,这样它可以根据元素的compareTo方法来比较元素的顺序。
如果元素没有实现Comparable接口,那么我们可以在创建ConcurrentSkipListSet 时提供一个比较器(Comparator),该比较器将用于确定元素的顺序。
在ConcurrentSkipListSet中,插入和删除操作的时间复杂度是O(log n),其中n是集合中的元素数量。
SkipList(跳表)分析
SkipList(跳表)分析最近上课讲了⼀个挺陌⽣的概念,叫做Skip List。
搜索了⼀下,中⽂名称作“跳表”。
写这个题⽬的原因:不过中⽂blog讲得都很浅:(刚刚baidu搜索了⼀下,百度⽂库⾥⾯有很好的介绍Skip List的⽂章:【1】 —— 线段跳表 —— 跳表的⼀个拓展 by ⽯家庄⼆中李骥扬【2】 —— 让算法的效率“跳起来”!—— by 华东师范⼤学第⼆附属中学魏冉不过,都缺乏了对应的分析部分。
)英⽂站点还是有很多详细的解释的:【3】我来尝试写⼀下,⾃⼰对于Skip List的理解。
希望能补全下不⾜的中⽂资料。
Skip List的原理:(如果感觉这⾥对于Skip List说明的不完整,请移步参考资料【1】/【2】/亲⾃google,本⽂重点在于理论分析+证明)产⽣动机:我们知道,如果⽤有序数组进⾏⼆分查找(Binary Search),则⽤时为O(log n)。
但有序数组的问题是:1. 它的容量有限,不能插⼊⽐它更多的元素;2. 每次增加⼀个元素,需要O(n)的时间,这样花费很⼤所以我们会选择⽤链表(Linked-List)来实现数据存储。
但链表的问题是,因为只能进⾏linear search,查找耗时O(n)。
因此,我们希望⼀种存储⽅式,能够在链表上实现O(log n)的查找时间。
⼀个最初的想法:每次查找的时候,因为元素是有序排列的,所以在进⾏查找的时候,⽐如查找数字7——即使跳过数字1~6,也不妨碍对7的发现。
所以,在查找元素的时候,如果能够看到⼏个元素之后的值,就能够决定是否要跳过这些元素。
从⽽缩短了查找的时间。
就好像⽕车,有快车有慢车;快车停得站少,慢车停得多。
所以,从⼀个地⽅到另⼀个地⽅,我们需要先乘坐快车,之后换乘慢车。
假设我们建⽴的⼀层快车,每两个元素直接跳过b个元素,那么每次查找的时间消耗(worst-case):T1 = n/b + b。
易知,当n/b = b时,T1最⼩。
Skip list层次的选择以及删除算法
Skip list层次的选择:插入列的“高度”较前者来说显得更加重要,也更加难以确定。
由于它的不确定性,使得不同的决策可能会导致截然不同的算法效率。
为了使插入数据之后,保持该数据结构进行各种操作均为O(logn)复杂度的性质,我们引入随机化算法(Randomized Algorithms)。
我们定义一个随机决策模块,它的大致内容如下:产生一个0到1的随机数r r ←random()如果r小于一个常数p,则执行方案A,if r<p then do A否则,执行方案B else do B初始时列高为1。
插入元素时,不停地执行随机决策模块。
如果要求执行的是A操作,则将列的高度加1,并且继续反复执行随机决策模块。
直到第i次,模块要求执行的是B操作,我们结束决策,并向跳跃表中插入一个高度为i的列。
性质1:根据上述决策方法,该列的高度大于等于k的概率为p^(k-1)。
此处有一个地方需要注意,如果得到的i比当前跳跃表的高度h还要大的话,则需要增加新的链,使得跳跃表仍满足先前所提到的条件。
自:《数据结构与算法》public void choosePowers(){powers[maxLevel-1]=(2<<(maxLevel))-1;for(int i=maxLevel-2,j=0;i>=0;i--,j++)powers[i]=powers[i+1]-(2<<j);}public int chooseLevel(){int i,r=Math.abs(rd.nextInt())%powers[maxLevel-1]+1;for(i=1;i<maxLevel;i++)if(r<powers[i])return i-1;return i-1;}假设最高层次maxLevel=4.则15个元素,在第一层需要8个节点,第二层需要4个,第三层需要2个,第四层需要1个。
每插入一个节点时,就生成一个1到15之间的随机数r (r=Math.abs(rd.nextInt())%powers[maxLevel-1]+1;),如果r<9则节点在第一层,如果r<13,那么节点插在第二层,如果r<15之间,那么节点插在第三层;而如果r=15,则生成第四层的节点并插入之。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 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