SkipList原理与实现
机制
链表中查询的效率的复杂度是O(n), 有没有办法提升这个查询复杂度呢? 最简单的想法就是在原始的链表上构建多层索引. 
在level 1(最底层为0), 每2位插入一个索引, 查询复杂度便是 O(N/2 + 1) 
在level 2, 每四位插入一个索引, 查询复杂度便是 O(N/4 + 2) 
那么推广开来, 如果我们有这样的一组链表, 在level i, 每间隔第 元素就有一个链接
在level 1, 每一个节点之间有一个链接 在level 2, 每两个节点之间有一个链接 在level 3, 每四个节点之间有一个链接 在level 4, 每八个节点之间有一个链接. 这样我们可以看到, 每向上一层, 数据量就减少了 1/2, 所以查询的过程就近似变成了2分查找, 查询性能就变成了稳定的O(logN). 索引的存储空间为 其中
两式相减得到 所以
所以
因此这样的数据结构总的空间复杂度为 2n - 2.
但是这样的数据结构存在一个问题, 严格要求每一层按照 的间隔链接很难在持续插入的过程中维护.
当插入一个新元素的时候, 需要为他分配一个新的节点, 此时我们需要决定该节点是多少阶的. 通过观察 Figure 10.60 可以发现, 有1/2的元素是1阶的, 有 1/4 的元素是2阶的, 所以大约 的节点是第 i 阶的. 那么根据这个性质, 我们就可以通过随机统计的方式来判断新元素应该插入的阶数. 最容易得做法就是抛一枚硬币直到正面出现并把抛硬币的总次数用作该节点的阶数.
连续抛i次才出现正面的概率是 , 而
的节点是属于第 i 阶的.
通常的计算阶数的方法
/**
* 这个函数返回的是levelCount, 最小为1, 表示不构建索引.
*
* <p>
* <li>1/2 概率返回1 表示不用构建索引
* <li>1/2 概率返回2 表示构建一级索引
* <li>1/4 概率返回3 表示构建二级索引
* <li>1/8 概率返回4 表示构建三级索引
*
* @return
*/
private int randomLevel() {
int level = 1;
while (Math.random() < SKIPLIST_P && level < MAX_LEVEL) {
level++;
}
return level;
}
通常p取值为 1/2 或者 1/4 表示两层之间的数据分布概率, Math.random()随机返回一个0-1之间的数, 这个就是模拟不断抛硬币的过程, height 为累计的抛硬币的次数.
因此跳表的实现, 是利用了随机化算法来计算新插入节点的阶数, 而这个阶数的数学期望能保证每一层数据能随机化的递减 1/2, 通过这样来保证最终插入和查找复杂度的期望都为 O(logN).
相比于红黑树 优势
插入 查找 删除的复杂度和红黑树一样
区间查找的效率更高
代码实现更简单
并且可以通过插入节点阶数生成的策略来平衡时间和空间复杂度的不同需求. 比如我们可以让每一层的数据为下一层的1/3. 这种情况下索引存储量 为 n/3 + n/9 + n/27 + ... 2 = n / 2 空间占用就缩小一半.
劣势
跳表的内存占用相比会大一点, 不过因为索引其实可以只存储key和指针, 实际的空间开销往往没有那么大
实现
查找
例如查找16
从head处开始查找, 同一层中遍历向前直到next节点为空或者next节点的value大于当前值
跳转到level + 1的索引处, 继续上述流程
最后一定会到达level 0. 指针非空则找到了相应的value
插入

通过随机函数生成此value插入的高度为2.
同查找一样的遍历流程, 找到比6大的那个位置. 在此过程中, 每一次向下迭代需要记录转折点如图中的1和4.这两个节点会作为6的前置节点
将新value的next指向原来前置节点的next, 将原来前置节点的next指向新的节点
同时新生成的level是可能高于原始高度的
删除
删除相比插入更简单, 在遍历每一层的时候不需要单独去记录前置节点了. 虽然以下实现是记录了前置节点后统一更新的, 但我感觉是没有必要的, delete可以改成 https://github.com/wangzheng0822/algo/blob/master/java/17_skiplist/SkipList.java
public void delete2(int value) {
Node p = head;
// 找到前置节点
for (int i = levelCount - 1; i >= 0; i--) {
while (p.forwards[i] != null && p.forwards[i].value < value) {
p = p.forwards[i];
}
if (p.forwards[i] != null && p.forwards[i].value == value) {
p.forwards[i] = p.forwards[i].forwards[i];
}
}
// head 指向为空的节点都剔除.
while (levelCount > 1 && head.forwards[levelCount] == null) {
levelCount--;
}
}
工业实现
redis的sorted set hbase中内存的有序集合 java ConcurrentSkipListSet ConcurrentSkipListMap
参考
<数据结构与算法分析 Java描述> 10.4.2
<HBase原理与实践> 2.1 跳跃表
https://zhuanlan.zhihu.com/p/33674267
SkipList原理与实现的更多相关文章
- 跳表(SkipList)原理篇
1.什么是跳表? 维基百科:跳表是一种数据结构.它使得包含n个元素的有序序列的查找和插入操作的平均时间复杂度都是 O(logn),优于数组的 O(n)复杂度.快速的查询效果是通过维护一个多层次的链表实 ...
- 浅析SkipList跳跃表原理及代码实现
本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈“跳跃表”的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代 ...
- 【转】浅析SkipList跳跃表原理及代码实现
SkipList在Leveldb以及lucence中都广为使用,是比较高效的数据结构.由于它的代码以及原理实现的简单性,更为人们所接受.首先看看SkipList的定义,为什么叫跳跃表? "S ...
- 算法: skiplist 跳跃表代码实现和原理
SkipList在leveldb以及lucence中都广为使用,是比较高效的数据结构.由于它的代码以及原理实现的简单性,更为人们所接受. 所有操作均从上向下逐层查找,越上层一次next操作跨度越大.其 ...
- 跳跃表 SkipList【数据结构】原理及实现
为什么选择跳表 目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等. 想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树出来吗? ...
- skiplist(跳表)的原理及JAVA实现
前记 最近在看Redis,之间就尝试用sortedSet用在实现排行榜的项目,那么sortedSet底层是什么结构呢? "Redis sorted set的内部使用HashMap和跳跃表(S ...
- JAVA SkipList 跳表 的原理和使用例子
跳跃表是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间),并且对并发算法友好. 关于跳跃表的具体介绍可以参考MIT的公开课:跳跃表 跳跃表的应 ...
- skip-list(跳表)原理及C++代码实现
跳表是一个很有意思的数据结构,它实现简单,但是性能又可以和平衡二叉搜索树差不多. 据MIT公开课上教授的讲解,它的想法和纽约地铁有异曲同工之妙,简而言之就是不断地增加“快线”,从而降低时间复杂度. 当 ...
- 跳跃表Skip List的原理和实现
>>二分查找和AVL树查找 二分查找要求元素可以随机访问,所以决定了需要把元素存储在连续内存.这样查找确实很快,但是插入和删除元素的时候,为了保证元素的有序性,就需要大量的移动元素了.如果 ...
- skiplist 跳表(2)-----细心学习
快速了解skiplist请看:skiplist 跳表(1) http://blog.sina.com.cn/s/blog_693f08470101n2lv.html 本周我要介绍的数据结构,是我非常非 ...
随机推荐
- Natasha V5.2.2.1 稳定版正式发布.
DotNetCore.Natasha.CSharp v5.2.2.1 使用 NMS Template 接管 CI 的部分功能. 取消 SourceLink.GitHub 的继承性. 优化几处内存占用问 ...
- GDB使用简单总结
简单总结常用gdb调试命令 不长篇讨论gdb是什么,或者怎么使用了,因为网上很多都讲的比较详细,以下只是做个备录,经常使用的命令,偶尔不用容易忘记! 1.set args xxxx (xxx为参数) ...
- ts中报错信息收集
1. 错误代码 参考:https://www.mmbyte.com/article/92849.html 1 state.localuserInfo = JSON.parse(localStorage ...
- Pwn系列之Protostar靶场 Stack1题解
(gdb) disasse main Dump of assembler code for function main: 0x08048464 <main+0>: push ebp 0x0 ...
- IIS6网站批量迁移至IIS7经验分享
迁移原因:公司服务器更换 迁移环境:源服务器 windows2003 X86 IIS6 目标服务器:windows2008 X64 IIS7 迁移过程: 第一次迁移失败,作为简要记 ...
- Python网页开发神器fac 0.2.8、fuc 0.1.28新版本更新内容介绍
fac项目地址:https://github.com/CNFeffery/feffery-antd-components fuc项目地址:https://github.com/CNFeffery/fe ...
- #Python实例 计算外卖配送距离(基于百度API接口)---第二篇
https://www.cnblogs.com/simone331/p/17218019.html 在上一篇中,我们计算了两点的距离(链接为上篇文章),但是具体业务中,往往会存在一次性计算多组,上百甚 ...
- 2022-11-19:第二高的薪水。表结构和数据的sql语句如下,输出200,因为200是第二大的。请问sql语句如何写? DROP TABLE IF EXISTS `employee`; CREAT
2022-11-19:第二高的薪水.表结构和数据的sql语句如下,输出200,因为200是第二大的.请问sql语句如何写? DROP TABLE IF EXISTS `employee`; CREAT ...
- 2020-11-16:手写代码:leetcode第406题。假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。
福哥答案2020-11-16: ①排序.按照[身高]降序排列.如果[身高]一样,按照[人数]升序排列.②插入.遍历这个队列,按照[人数]插入相应位置. 采用leetcode里的代码,golang代码如 ...
- 2021-12-01:给定一个正数数组arr,代表每个人的体重。给定一个正数limit代表船的载重,所有船都是同样的载重量。 每个人的体重都一定不大于船的载重。 要求: 1, 可以1个人单独一搜船;
2021-12-01:给定一个正数数组arr,代表每个人的体重.给定一个正数limit代表船的载重,所有船都是同样的载重量. 每个人的体重都一定不大于船的载重. 要求: 1, 可以1个人单独一搜船: ...