数据结构进阶篇-跳表

大家想必都知道,数组和链表的搜索操作的时间复杂度都是O(N)的,在数据量大的时候是非常耗时的。对于数组来说,我们可以先排序,然后使用二分搜索,就能够将时间复杂度降低到O(logN),但是有序数组的插入是一个O(N)级别的操作。而链表的插入性能相对优秀,却不能使用二分搜索快速查询。那么是否有一种数据结构,即能够像链表一样快速插入数据,又支持类似于二分搜索这样的查询算法呢?答案是肯定的。William Pugh教授在1990发表的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》中提出的跳表就是这样一种有趣的数据结构。

跳表的结构

跳表的核心思想是通过建立索引层来缩短链表的搜索路径,以达到快速搜索的目的。
假设我们从链表中的每两个节点中提取出一个建立一级索引,然后再从每两个一级索引中提取一个建立二级索引,以此类推,就可以得到如下图所示的结构,其中绿色节点表示索引。

在William Pugh的论文中使用了数组加链表的组合来实现跳表,就如上图所示,每一列索引具有相同的key,使用一个数组来表示。还可以使用纯链表的形式来实现跳表,我觉得这种方式更有助于理解跳表的原理,如下图所示。

跳表的搜索

跳表的搜索需要从高层索引开始向下逐层搜索,每一层的搜索方式和普通链表是一样的,当后继节点的关键字大于搜索关键字时结束本层的搜索,进入下一层继续搜索。下图展示了跳表搜索关键字 22 的过程,其中红色部分就是搜索的路径。

从上图可以很直观的看出,跳表的搜索和二分搜索是一样的,其时间复杂度也是O(logN)的,我们不妨简单证明一下。
假设跳表中有N个数据节点(关键字),每m个低级索引(或数据节点)中提取出一个作为高级索引,那么
一级索引的数量 L_1 = \frac{N}{m}
二级索引的数量 L_2 = \frac{N}{m^2}
三级索引的数量 L_3 = \frac{N}{m^3}
以此类推,第i级索引的数量 L_i = \frac{N}{m^i}
最高级索引的数量 L_{max} = m = \frac{N}{m^{max}}
所以索引的最大层级 MaxLevel = \log_m{N} - 1
每一层的搜索次数 M \leq m
所以跳表的搜索次数 \Theta  \leq M \cdot MaxLevel = m \cdot (\log_m{N} - 1)
因为m是一个常量,因此跳表的搜索时间复杂度是O(logN)的

跳表的多层索引结构使它的搜索方式非常灵活且强大
比如我们可能有这样的需求,如果key不存在,我们需要知道这个key邻近的nearKey是什么,这用跳表很容易实现

  1. 搜索比key小且最接近key的关键字lowerKey,如上图所示,后继节点大于等于key时,直接返回当前节点即可
  2. 搜索比key大且最接近key的关键字higherKey,如上图所示,后继节点大于key时,直接返回后继节点即可

跳表还可以很容易的搜索一个关键字区间[fromKey, toKey],这点和B+树很类似,先搜索fromKey,然后向后遍历链表,取出所有小于等于toKey的数据即可

跳表的插入

到现在为止,本文描述的都是理想状态下的跳表,事实上,我们不会严格的为跳表的每m个低级索引建立高级索引,因为这样做复杂而且低效。所以William Pugh在他的论文中采用一种随机算法来为每个新增的节点随机建立索引,下面是我用Java实现的版本。

int randomLevel(int m, int maxLevel) {ThreadLocalRandom r = ThreadLocalRandom.current();int level = 1;while (r.nextInt(m) == 0 && level <  maxLevel)level++;return level;
}
复制代码

通过这种随机算法,生成第i级索引的概率为 \frac{1}{m^i}
所以能够保证每一层索引的数量都接近于 \frac{N}{m^i},这正好符合我们前面提到的索引层的性质。
Doug Lea大佬在Java的ConcurrentSkipListMap中使用了另外一种更加炫酷的随机算法的实现方式,使用随机数末尾连续为1的位数作为索引的等级,显然这种方式生成第i级索引的概率为 \frac{1}{2^i} ,代码如下所示。

int rn = ThreadLocalRandom.current().nextInt();
// 只有最高位和最低位都为0时,才建立索引,相当于为4个node建立一个索引
if ((rn & 0x80000001) == 0) {int level = 1;// 建立索引的等级等于rn末尾连续为1的位数while (((rn >>>= 1) & 1) != 0)level++;
}
复制代码

通过随机函数生成一个随机的索引等级之后,创建一个新的索引列,并将每一层的新索引链接到它的前驱索引的后面,如果生成的随机等级大于当前跳表的最大索引等级,需要添加一层新的索引。如下图所示,其中红色虚线箭头表示重新建立的链接。

跳表的删除

跳表的删除操作比较简单,先查询删除的关键字,如果在索引层匹配到了关键字,就向下删除所有的索引和数据节点,如果没有匹配到索引,只需要删除数据节点即可。其中有一点需要注意的是,在删除索引后需要检测一下,如果当前层的HEAD索引的后继索引为NIL,则表示这一层已经没有索引了,需要删除这个索引层。如下图所示,红色箭头表示重新建立的链接。

跳表的实现

跳表的实现相对AVL树、红黑树等平衡二叉树来说简单了很多,William Pugh的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》中提供了使用数组加链表实现跳表的伪代码,我写了一个Java版本的纯链表实现的跳表,并上传到了我的GitHub上,有兴趣的朋友可以看一下。如果你需要在开发中使用跳表的话,java.util.concurrent.ConcurrentSkipListMap是一个强大的实现,而且它还是线程安全的。

转载于:https://juejin.im/post/5cdc38236fb9a0322d04ac7b

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/448807.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

查看本机ssh公钥,生成公钥

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 查看ssh公钥方法&#xff1a; 1.通过命令窗口&#xff1a;打开你的git bash 窗口&#xff0c;进入.ssh目录&#xff1a;cd ~/.ssh&…

如何实现动态水球图 --》 echars结合echarts-liquidfill实现

1&#xff09;项目中作为项目依赖&#xff0c;安装到项目当中(注意必须要结合echars) npm install echarts vue-echarts --save npm install echarts-liquidfill --save 2&#xff09;在需要使用水晶球的组件里引入liquidFill.js import echarts-liquidfill/src/liquidFill.js;…

RabbitMQ 从入门到精通 (一)

目录 1. 初识RabbitMQ2. AMQP3.RabbitMQ的极速入门4. Exchange(交换机)详解4.1 Direct Exchange4.2 Topic Exchange4.3 Fanout Exchange5. Message 消息1. 初识RabbitMQ RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用来通过普通协议在完全不同的应用之间共享数据&a…

接收并解析消息体传参、解析 json 参数

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1.场景&#xff1a;postman 发送了一个 post 请求&#xff0c;如下&#xff1a; 2. 解析方式为用一个 vo 对象来接收 json。把 json 中的…

OpenCL memory object 之 传输优化

首先我们了解一些优化时候的术语及其定义&#xff1a; 1、deferred allocation&#xff08;延迟分配&#xff09;&#xff0c; 在第一次使用memory object传输数据时&#xff0c;runtime才对memory object真正分配空间。 这样减少了资源浪费&#xff0c;但第一次使用时要慢一些…

VBS使文本框的光标位于所有字符后

有时候在文本框里会显示一部分提示信息&#xff0c;用户在这些提示信息后面输入文本&#xff0c;但是将焦点设置于文本框后&#xff0c;光标总是在文本框的最前面&#xff0c; 用户输入的时候需要按"-->"键将光标移到最后才能输入&#xff0c;这样的操作很不爽。我…

AMD OpenCL 大学课程

AMD OpenCL大学课程是非常好的入门级OpenCL教程&#xff0c;通过看教程中的PPT&#xff0c;我们能够很快的了解OpenCL机制以及编程方法。下载地址&#xff1a;http://developer.amd.com/zones/OpenCLZone/universities/Pages/default.aspx 教程中的英文很简单&#xff0c;我相信…

47.QT-QChart之曲线图,饼状图,条形图使用

1.使用准备 在pro中, 添加QT charts 然后在界面头文件中添加头文件并声明命名空间,添加: #include <QtCharts> QT_CHARTS_USE_NAMESPACE 2.QChart之曲线图 绘制曲线图需要用到3个类 QSplineSeries: 用于创建有由一系列数据组成的曲线.类似的还有QPieSeries(饼图数据). Q…

Docker 部署应用、jar 工程 docker 方式部署

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 把要部署的工程打成一个jar包。&#xff08;我的工程叫 gentle &#xff09; 打 jar 的方法&#xff1a;超简单方法&#xff1a; Int…

第二阶段冲刺(2)

1、整个项目预期的任务量 &#xff08;任务量 所有工作的预期时间&#xff09;和 目前已经花的时间 &#xff08;所有记录的 ‘已经花费的时间’&#xff09;&#xff0c;还剩余的时间&#xff08;所有工作的 ‘剩余时间’&#xff09; &#xff1b; 所有工作的预期时间&#…

华为路由器配置DHCP中继

DHCP(动态主机配置协议)理论知识&#xff1a;DHCP主要用来为客户机自动配置I P地址相关的网络参数&#xff0c;包括IP地址、子网掩码、默认网关、DNS服务器等。 DHCP 通信为广播的方式&#xff0c;因此当需要 DHCP 服务器为不同广播域&#xff08;路由或 VLAN 网段&#xff09;…

基于GPU的K-Means聚类算法

聚类是信息检索、数据挖掘中的一类重要技术&#xff0c;是分析数据并从中发现有用信息的一种有效手段。它将数据对象分组成为多个类或簇&#xff0c;使得在同一个簇中的对象之间具有较高的相似度&#xff0c;而不同簇中的对象差别很大。作为统计学的一个分支和一种无监督的学习…

GPU通用计算调研报告

摘要&#xff1a;NVIDIA公司在1999年发布GeForce256时首先提出GPU&#xff08;图形处理器&#xff09;的概念&#xff0c;随后大量复杂的应用需求促使整个产业蓬勃发展至今。GPU在这十多年的演变过程中&#xff0c;我们看到GPU从最初帮助CPU分担几何吞吐量&#xff0c;到Shader…

git 图形化工具 GitKraken 的使用 —— 分支的创建与合并

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 分支管理是Git工作流的重点 在之前的文章中通过GitKraken可以很清楚的看到&#xff0c;每一次commit&#xff0c;git把他们串成了一条线…

GitKraken - 简单教程

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 简单介绍&#xff1a;外观 GitKraken首页预览图 常用快捷键 模糊搜索&#xff1a;(cmd p) 在进行模糊搜索的时候会在当前页面弹出一个…

LeetCode刷题第二天——3Longest Substring Without repeating character 4 Median of Two Sorted Arrays...

混淆点&#xff1a; 子串 连续 子序列 可以不连续 知识点&#xff1a; HashMap&#xff1a; 出现问题&#xff1a; 1.使用unordered_map头文件时报错 #error This file requires compiler and library support for the ISO C 2011 standard. This support is currently experi…

【BZOJ 3339 / BZOJ 3585 / luogu 4137】Rmq Problem / mex

【原题题面】传送门 【题解大意】 都说了是莫队练习题。 考虑已知[l,r]区间的mex值时&#xff0c;如何求[l1,r]的mex值。 比较a[l1]与已知ans的大小&#xff0c;如果a[l1]>ans或者a[l1]<ans&#xff0c;均对答案没有影响。 如果a[l1]ans&#xff0c;考虑找到一个比当前an…

postman 无法正常返回结果 Could not get any response

在浏览器输入地址可以返回结果&#xff0c;但是由于返回的json没有格式&#xff0c;看起来比较麻烦&#xff0c;用postman却报错Could not get any response。 可以注意到下面写了可能的情况&#xff1a;比如服务器无响应&#xff08;由于浏览器可以访问&#xff0c;所以排除…

在Windows 下使用OpenCL

目前&#xff0c;NVIDIA和AMD的Windows driver均有支援OpenCL&#xff08;NVIDIA的正式版driver是从195.62版开始&#xff0c;而AMD则是从9.11版开始&#xff09;。NVIDIA的正式版driver中包含OpenCL.dll&#xff0c;因此可以直接使用。AMD到目前为止&#xff0c;则仍需要安装其…

[Swift]快速反向平方根 | Fast inverse square root

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号&#xff1a;山青咏芝&#xff08;shanqingyongzhi&#xff09;➤博客园地址&#xff1a;山青咏芝&#xff08;https://www.cnblogs.com/strengthen/&#xff09;➤GitHub地址&a…