机制

链表中查询的效率的复杂度是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. 插入 查找 删除的复杂度和红黑树一样

  2. 区间查找的效率更高

  3. 代码实现更简单

  4. 并且可以通过插入节点阶数生成的策略来平衡时间和空间复杂度的不同需求. 比如我们可以让每一层的数据为下一层的1/3. 这种情况下索引存储量 为 n/3 + n/9 + n/27 + ... 2 = n / 2 空间占用就缩小一半.

劣势

  1. 跳表的内存占用相比会大一点, 不过因为索引其实可以只存储key和指针, 实际的空间开销往往没有那么大

实现

查找

例如查找16

  1. 从head处开始查找, 同一层中遍历向前直到next节点为空或者next节点的value大于当前值

  2. 跳转到level + 1的索引处, 继续上述流程

  3. 最后一定会到达level 0. 指针非空则找到了相应的value

插入

  1. 通过随机函数生成此value插入的高度为2.

  2. 同查找一样的遍历流程, 找到比6大的那个位置. 在此过程中, 每一次向下迭代需要记录转折点如图中的1和4.这两个节点会作为6的前置节点

  3. 将新value的next指向原来前置节点的next, 将原来前置节点的next指向新的节点

  4. 同时新生成的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 跳跃表

王争数据结构与算法之美#17

Skip List--跳表(全网最详细的跳表文章没有之一)

https://zhuanlan.zhihu.com/p/33674267

SkipList原理与实现的更多相关文章

  1. 跳表(SkipList)原理篇

    1.什么是跳表? 维基百科:跳表是一种数据结构.它使得包含n个元素的有序序列的查找和插入操作的平均时间复杂度都是 O(logn),优于数组的 O(n)复杂度.快速的查询效果是通过维护一个多层次的链表实 ...

  2. 浅析SkipList跳跃表原理及代码实现

    本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈“跳跃表”的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代 ...

  3. 【转】浅析SkipList跳跃表原理及代码实现

    SkipList在Leveldb以及lucence中都广为使用,是比较高效的数据结构.由于它的代码以及原理实现的简单性,更为人们所接受.首先看看SkipList的定义,为什么叫跳跃表? "S ...

  4. 算法: skiplist 跳跃表代码实现和原理

    SkipList在leveldb以及lucence中都广为使用,是比较高效的数据结构.由于它的代码以及原理实现的简单性,更为人们所接受. 所有操作均从上向下逐层查找,越上层一次next操作跨度越大.其 ...

  5. 跳跃表 SkipList【数据结构】原理及实现

    为什么选择跳表 目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等. 想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树出来吗? ...

  6. skiplist(跳表)的原理及JAVA实现

    前记 最近在看Redis,之间就尝试用sortedSet用在实现排行榜的项目,那么sortedSet底层是什么结构呢? "Redis sorted set的内部使用HashMap和跳跃表(S ...

  7. JAVA SkipList 跳表 的原理和使用例子

    跳跃表是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间),并且对并发算法友好. 关于跳跃表的具体介绍可以参考MIT的公开课:跳跃表 跳跃表的应 ...

  8. skip-list(跳表)原理及C++代码实现

    跳表是一个很有意思的数据结构,它实现简单,但是性能又可以和平衡二叉搜索树差不多. 据MIT公开课上教授的讲解,它的想法和纽约地铁有异曲同工之妙,简而言之就是不断地增加“快线”,从而降低时间复杂度. 当 ...

  9. 跳跃表Skip List的原理和实现

    >>二分查找和AVL树查找 二分查找要求元素可以随机访问,所以决定了需要把元素存储在连续内存.这样查找确实很快,但是插入和删除元素的时候,为了保证元素的有序性,就需要大量的移动元素了.如果 ...

  10. skiplist 跳表(2)-----细心学习

    快速了解skiplist请看:skiplist 跳表(1) http://blog.sina.com.cn/s/blog_693f08470101n2lv.html 本周我要介绍的数据结构,是我非常非 ...

随机推荐

  1. 苞米豆的多数据源 → dynamic-datasource-spring-boot-starter,挺香的!

    开心一刻 2023年元旦,我妈又开始了对我的念叨 妈:你到底想多少岁结婚 我:60 妈:60,你想找个多大的 我:找个55的啊,她55我60,结婚都有退休金,不用上班不用生孩子,不用买车买房,成天就是 ...

  2. ABC267G Increasing K Times 题解

    做这道题,很有感悟,发篇文. 先给数列从小到大排个序. 接下来设 \(f_{i,j}\) 表示前 \(i\) 个数的排列形成 \(j\) 个上坡的方案数. 接下来考虑转移,分为插入第 \(i\) 个数 ...

  3. Azure DevOps(三)Azure Pipeline 自动化将程序包上传到 Azure Bolb Storage

    一,引言 结合前几篇文章,我们了解到 Azure Pipeline 完美的解决了持续集成,自动编译.同时也兼顾了 Sonarqube 作为代码扫描工具.接下来另外一个问题出现了,Azure DevOp ...

  4. Prism Sample 13-IActiveAwareCommands

    本例和12的唯一区别,仅仅是在ViewModel中增加了一个IActiveAware,这决定了只有在Acitve状态的视图中才会执行自己ViewModel中的命令.

  5. #PowerBi 1分钟学会,以“万”为单位显示数据

    PowerBi是一款强大的数据分析和可视化工具,它可以帮助我们快速地制作出各种图表和报表,展示数据的价值和洞察. 但是,有时候我们的数据量太大,导致图表上的数字难以阅读和比较.例如,如果我们想要查看某 ...

  6. 音视频八股文(3)--ffmpeg常见命令(2)

    07-ffplay命令播放媒体 播放本地文件 播放本地 MP4 视频文件 test.mp4 的命令,从第 2 秒位置开始播放,播放时长为 10 秒,并且在窗口标题中显示 "test time ...

  7. 2022-06-23:给定一个非负数组,任意选择数字,使累加和最大且为7的倍数,返回最大累加和。 n比较大,10的5次方。 来自美团。3.26笔试。

    2022-06-23:给定一个非负数组,任意选择数字,使累加和最大且为7的倍数,返回最大累加和. n比较大,10的5次方. 来自美团.3.26笔试. 答案2022-06-23: 要i还是不要i,递归. ...

  8. 2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作。 比如你选定1下标,

    2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作. 比如你选定1下标, ...

  9. 认识 CPU 底层原理(2)——逻辑门

    本文为B站UP主硬件茶谈制作的系列科普<[硬件科普]带你认识CPU>系列的学习笔记,仅作个人学习记录使用,如有侵权,请联系博主删除 上一篇文章我们从最基本的粒子的角度认识了组成CPU的最基 ...

  10. 解决:Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。

    启动django应用时报如下错误:Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试. 1.首先退出酷狗音乐再试试 2.是8000端口被其他程序占用了, ...