查找——图文翔解SkipList(跳跃表)
跳跃表
跳跃列表(也称跳表)是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作须要O(logn)平均时间)。
基本上。跳跃列表是对有序的链表添加上附加的前进链接,添加是以随机化的方式进行的。所以在列表中的查找能够高速的跳过部分列表元素,因此得名。全部操作都以对数随机化的时间进行。
例如以下图所看到的。是一个即为简单的跳跃表。
传统意义的单链表是一个线性结构。向有序的链表中插入一个节点须要O(n)的时间。查找操作须要O(n)的时间。假设我们使用图中所看到的的跳跃表。就能够大大降低降低查找所需时间。
由于我们能够先通过每一个节点的最上层的指针先进行查找,这样子就能跳过大部分的节点。然后再缩减范围,对以下一层的指针进行查找,若仍未找到,缩小范围继续查找。
上面基本上就是跳跃表的思想。每个结点不单单仅仅包括指向下一个结点的指针。可能包括非常多个指向兴许结点的指针,这样就能够跳过一些不必要的结点,从而加快查找、删除等操作。对于一个链表内每一个结点包括多少个指向兴许元素的指针,这个过程是通过一个随机函数生成器得到。这样子就构成了一个跳跃表。
构造
由图不难理解跳跃表的原理,能够看出。跳跃表中的一个节点是有不同数目的后继指针的。
那么问题来了,这详细是怎样实现的?这些节点是怎样构造的?
【分析】
我们不可能为每一种后继指针数目的节点分配一种大小类型的节点,那我们就提取共性,看这些节点有何共通之处。
这些节点可看做由两部分构成:数据域、指针域。
数据域包含key-Value,指针域是后继指针的集合。
那怎样在节点中保存后继指针集合呢?用一个二级指针,分配节点的时候指向动态分配的后继指针数组。这个方法似乎可行,但问题在于我们的节点也是动态分配的,这种话,在释放节点的时候还须要先释放节点中动态分配的数组。
释放操作比較繁琐。
灵光一闪!之前本博客中介绍了一种称为“零数组”的技术,或许能够帮到我们。(详情点击)
零数组是gcc的扩展特性。只是在C99中,能够用类似的声明来实现。
struct Node{
KeyType key;
ValueType value;
struct Node* forward[0]; //C99这样玩:struct Node* forward[]
};
动态分配节点能够这样写:
(struct Node *)malloc(sizeof(struct Node) + length*sizeof(struct Node*)); //length是后继指针数组的长度
这种话。我们能够像訪问数组那样訪问forward。且释放的时候仅仅释放动态分配的节点就可以。
(forward仅仅是起一个标记的作用)
当然,另一种更通用的技巧,和零数组的思想类似
struct Node{
KeyType key;
ValueType value;
struct Node* forward[1];
};
我们在这里用符合不论什么C标准的定义,定义一个1个元素的数组,用来占位。然后动态分配一大块空间。我们通过对这个1元素数组的越界訪问,訪问到其后分配的空间。
我们能够定义一个函数专门负责分配不同大小的节点:
void NewNodeWithLevel(const int& level, struct Node& node){
//新结点空间大小
int total_size = sizeof(struct Node) + level*sizeof(struct Node*);
//申请空间
node = (struct Node)malloc(total_size);
assert(node != NULL);
}
查找
我们以查找19为例。图解查找过程。
先从最上层的跳跃区间大的层開始查找。从头结点開始。首先和23进行比較,小于23,(此时查找指针在图中“1”位置处)。查找指针到下一层继续查找。
然后和9进行推断,大于9,查找指针再往前走一步和23比較,小于23。(此时查找指针在图中“2”位置处) 此时这个值肯定在9结点和23结点之间。查找指针到下一层继续查找。
然后和13进行推断。大于13,查找指针再往前走一步和23比較,小于23,(此时查找指针在图中“3”位置处)此时这个值肯定在13结点和23结点之间。查找指针到下一层继续查找。
此时,我们和19进行推断。找到了。
好了,看完这个样例,你一定对跳转表的查找操作有了清晰的理解。至于代码实现也不难了。
插入
插入和删除的实现很像对应的链表操作。除了"高层"元素必须在多个链表中插入或删除之外。
插入包括例如以下几个操作:1、查找到须要插入的位置 2、申请新的结点 3、调整指针。
由于找到插入点之后,新生成节点,新节点按概率出如今每层上,故须要保存全部层的后继指针。我们用一个暂时数组保存全部层的插入点处的后继指针。
在寻找插入点的时候就能够完毕赋值。
for(i = list->level; i >= 0; --i){
while(x->forward[i]->key < key){
x = x->forward[i];
}
update[i] = x;
}
删除
删除操作类似于插入操作,包括例如以下3步:1、查找到须要删除的结点 2、删除结点 3、调整指针
相同。须要一个暂时数组保存每层的指针域。原理和插入类似。不再赘述。
【关于释放跳转表】
释放表的操作比較简单,仅仅要像单链表一样释放表就可以。
【跳跃表使用概率均衡技术而不是使用强制性均衡,因此,对于插入和删除结点比传统上的平衡树算法更为简洁高效。
】
分析
跳跃列表是按层建造的。底层是一个普通的有序链表。每一个更高层都充当以下列表的“高速跑道”,这里在层 i 中的元素按某个固定的概率 p (通常为0.5或0.25)出如今层 i+1 中。平均起来。每一个元素都在 1/(1-p) 个列表中出现,而最高层的元素(一般是在跳跃列表前端的一个特殊的头元素)在 O(log1/p n) 个列表中出现。
要查找一个目标元素。起步于头元素和顶层列表,并沿着每一个链表搜索。直到到达小于或着等于目标的最后一个元素。
在每一个链表中预期的查找步数显而易见是 1/p。所以查找的整体代价是 O((log1/p n) / p),当p 是常数时是 O(log n)。通过选择不同 p 值。就能够在查找代价和存储代价之间作出权衡。
跳跃列表不像某些传统平衡树数据结构那样提供绝对的最坏情况性能保证,由于用来建造跳跃列表的扔硬币方法总有可能(虽然概率非常小)生成一个糟糕的不平衡结构。可是在实际中它工作的非常好。随机化平衡方案比在平衡二叉查找树中用的确定性平衡方案easy实现。跳跃列表在并行计算中也非常实用,这里的插入能够在跳跃列表不同的部分并行的进行,而不用全局的数据结构又一次平衡。
【性能】
空间复杂度:O(n)
查找、插入和删除操作的时间复杂度都为: O(logn)
随机跳跃表表现性能也非常不错,节省了大量复杂的调节平衡树的代码。其效率与红黑树、伸展树等这些平衡树能够说相差不大。
但跳跃表还在并发环境下有优势。在并发环境下。假设要更新数据,跳跃表须要更新的部分就比較少,锁的东西也就比較少。所以不同线程争锁的代价就相对少了,而红黑树有个平衡的过程,牵涉到大量的节点,争锁的代价也就相对较高了。
性能也就不如前者了。
应用
了解过Redis的都知道,Redis有一个非常实用的数据结构:SortSet,基于它,我们能够非常轻松的实现一个Top N的应用。
这个SortSet底层就是利用跳表实现的。
跳表也被用在leveldb中。在一些词典结构中中也经经常使用跳表来实现字典,加快查找速度。
总结
Skip lists和进行过优化的平衡树有着相同高的性能,Skip lists的性能远远超过未经优化的平衡二叉树。
“跳跃列表是在非常多应用中有可能替代平衡树而作为实现方法的一种数据结构。跳跃列表的算法有同平衡树一样的渐进的预期时间边界,而且更简单、更高速和使用更少的空间。”
【參考】
关于代码參考这里:http://blog.csdn.net/ict2014/article/details/17394259
查找——图文翔解SkipList(跳跃表)的更多相关文章
- 查找——图文翔解HashTree(哈希树)
引 在各种数据结构(线性表.树等)中,记录在结构中的相对位置是随机的.因此在机构中查找记录的时须要进行一系列和keyword的比較.这一类的查找方法建立在"比較"的基础上.查找的效 ...
- 查找——图文翔解Treap(树堆)
之前我们讲到二叉搜索树,从二叉搜索树到2-3树到红黑树到B-树. 二叉搜索树的主要问题就是其结构与数据相关,树的深度可能会非常大,Treap树就是一种解决二叉搜索树可能深度过大的还有一种数据结构. T ...
- 浅析SkipList跳跃表原理及代码实现
本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈“跳跃表”的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代 ...
- redis skiplist (跳跃表)
redis skiplist (跳跃表) 概述 redis skiplist 是有序的, 按照分值大小排序 节点中存储多个指向其他节点的指针 结构 zskiplist 结构 // 跳跃表 typede ...
- 小白也能看懂的Redis教学基础篇——朋友面试被Skiplist跳跃表拦住了
各位看官大大们,双节快乐 !!! 这是本系列博客的第二篇,主要讲的是Redis基础数据结构中ZSet(有序集合)底层实现之一的Skiplist跳跃表. 不知道那些是Redis基础数据结构的看官们,可以 ...
- 【Redis】skiplist跳跃表
有序集合Sorted Set zadd zadd用于向集合中添加元素并且可以设置分值,比如添加三门编程语言,分值分别为1.2.3: 127.0.0.1:6379> zadd language 1 ...
- 【转】浅析SkipList跳跃表原理及代码实现
SkipList在Leveldb以及lucence中都广为使用,是比较高效的数据结构.由于它的代码以及原理实现的简单性,更为人们所接受.首先看看SkipList的定义,为什么叫跳跃表? "S ...
- SkipList 跳跃表
引子 考虑一个有序表:14->->34->->50->->66->72 从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 ...
- 详解SkipList跳跃链表【含代码】
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天继续介绍分布式系统当中常用的数据结构,今天要介绍的数据结构非常了不起,和之前介绍的布隆过滤器一样,是一个功能强大原理简单的数据结构.并且 ...
随机推荐
- 通用的高度可扩展的Excel导入实现(附Demo)
Demo源码 背景 通过程序将excel导入到数据库中是一项非常常见的功能.通常的做法是:先将excel转成DataTable,然后将DataTable转换成List<T>,最终通过Lis ...
- Leetcode207--->课程表(逆拓扑排序)
题目: 课程表,有n个课程,[0, n-1]:在修一个课程前,有可能要修前导课程: 举例: 2, [[1,0]] 修课程1前需要先修课程0 There are a total of 2 courses ...
- Spider爬虫-get、post请求
1:概念: 爬虫就是通过编写程序,模拟浏览器上网,然后让其去互联网上抓取数据的过程. 2:python爬虫与其他语言的比较: (1)php爬虫弊端:多进程多线程支持的不好 (2)java:代码臃肿,重 ...
- 指定特殊的安装目录用configure进行配置
linux - Make install, but not to default directories? - Stack Overflow I want to run 'make install' ...
- Welcome-to-Swift-13继承(Inheritance)
一个类可以继承(inherit)另一个类的方法(methods),属性(property)和其它特性.当一个类继承其它类时,继承类叫子类(subclass),被继承类叫超类(或父类,superclas ...
- 【Luogu】P2567幸运数字(容斥爆搜)
题目链接 先预处理出幸运数,把成倍数关系的剔掉,然后用容斥原理搜索一下. 这里的容斥很像小学学的那个“班上有n个同学,有a个同学喜欢数学,b个同学喜欢语文……”那样. #include<cstd ...
- Echarts学习总结(一)-----柱状图
简介 ECharts,缩写来自Enterprise Charts,商业级数据图表,基于[HTML5]Canvas (ZRender),纯Javascript图表库,是百度的一个开源的数据可视化工具,业 ...
- SpringBoot重点详解--使用Junit进行单元测试
目录 添加依赖与配置 ApplicationContext测试 Environment测试 MockBean测试 Controller测试 情况一 情况二 方法一 方法二 本文将对在Springboo ...
- Spring Boot 配置大全
Spring Boot 允许通过外部配置让你在不同的环境使用同一应用程序的代码,简单说就是可以通过配置文件来注入属性或者修改默认的配置. SpringBoot的配置方式有很多,它们的优先级如下所示(优 ...
- ie下table无法设置宽度的坑,解决方案:在td里面添加div把td宽度撑开即可。
<td><div style="width:180px"> <a data-b="2" class="btn btn-s ...