算法通关村番外篇-跳表

大家好我是苏麟 , 今天来聊聊调表 .

跳表很少很少实现所以我们只了解就可以了 . 

跳表

链表在查找元素的时候,因为需要逐一查找,所以查询效率非常低,时间复杂度是O(N),于是就出现了跳表。跳表是在链表基础上改进过来的,实现了一种「多层」的有序链表,这样的好处是能快读定位数据。

那跳表长什么样呢?我这里举个例子,下图展示了一个层级为 3 的跳表。

图中头节点有 L0~L2 三个头指针,分别指向了不同层级的节点,然后每个层级的节点都通过指针连接起来:

  • L0 层级共有 5 个节点,分别是节点1、2、3、4、5;
  • L1 层级共有 3 个节点,分别是节点 2、3、5;
  • L2 层级只有 1 个节点,也就是节点 3 。

如果我们要在链表中查找节点 4 这个元素,只能从头开始遍历链表,需要查找 4 次,而使用了跳表后,只需要查找 2 次就能定位到节点 4,因为可以在头节点直接从 L2 层级跳到节点 3,然后再往前遍历找到节点 4。

可以看到,这个查找过程就是在多个层级上跳来跳去,最后定位到元素。当数据量很大时,跳表的查找复杂度就是 O(logN)。

跳表节点层数设置

跳表的相邻两层的节点数量的比例会影响跳表的查询性能。

举个例子,下图的跳表,第二层的节点数量只有 1 个,而第一层的节点数量有 6 个。

这时,如果想要查询节点 6,那基本就跟链表的查询复杂度一样,就需要在第一层的节点中依次顺序查找,复杂度就是 O(N) 了。所以,为了降低查询复杂度,我们就需要维持相邻层结点数间的关系。

跳表的相邻两层的节点数量最理想的比例是 2:1,查找复杂度可以降低到 O(logN)

下图的跳表就是,相邻两层的节点数量的比例是 2 : 1。

那怎样才能维持相邻两层的节点数量的比例为 2 : 1 呢?

如果采用新增节点或者删除节点时,来调整跳表节点以维持比例的方法的话,会带来额外的开销。

Redis 则采用一种巧妙的方法是,跳表在创建节点的时候,随机生成每个节点的层数,并没有严格维持相邻两层的节点数量比例为 2 : 1 的情况。

具体的做法是,跳表在创建节点时候,会生成范围为[0-1]的一个随机数,如果这个随机数小于 0.25(相当于概率 25%),那么层数就增加 1 层,然后继续生成下一个随机数,直到随机数的结果大于 0.25 结束,最终确定该节点的层数

这样的做法,相当于每增加一层的概率不超过 25%,层数越高,概率越低,层高最大限制是 64。

虽然我前面讲解跳表的时候,图中的跳表的「头节点」都是 3 层高,但是其实如果层高最大限制是 64,那么在创建跳表「头节点」的时候,就会直接创建 64 层高的头节点

跳表的插入与删除

假设我们要插入的节点是10,首先找到待插节点的前置节点(仅小于待插入节点):

接下来,按照一般链表的插入方式,把节点10插到节点9的下一个 位置:

这样是不是插入工作就完成了呢?并不是。随着原始链表的新节 点越来越多,索引会渐渐变得不够用了,因此索引节点也需要相应做 出调整

如何调整索引呢?我们让新插入的节点随机“晋升”,也就是成 为索引节点。新节点晋升成功的几率是50%。

假设第一次随机的结果是晋升成功,那么我们把节点10作为索引 节点,插到第1层索引的对应位置,并且向下指向原始链表的节点10:

新节点在成功晋升之后,仍然有机会继续向上一层索引晋升。我 们再进行一次随机,假设随机的结果是晋升失败,那么插入操作就告 一段落了。

新节点10已经晋升到 第2层索引,下一次随机的结果仍然是晋升成功,这时候我们该怎么 办?

这时候,我们直接让索引增加一层,就像下面这样:

至于跳表删除节点的过程,则是相反的思路。

假设我们要从跳表中删除节点10,首先按照跳表查找节点的方 法,找到待删除的节点:

接下来,按照一般链表的删除方式,把节点10从原始链表当中删 除:

这样是不是删除工作就完成了呢?并不是。我们需要顺藤摸瓜, 把索引当中的对应节点也一一删除:

那么,如果某一层索引的节点被删光了,该怎么办呢?

很好解决,直接把没有节点的那一层删去就好了。

在刚才的例子当中,第3层索引的节点已经没有了,因此我们把整 个第3层删去:

最终的删除结果如下:


这期就到这里 , 下期见!

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

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

相关文章

回环屏障CyclicBarrier原理探究

上节介绍的CountDownLatch在解决多个线程同步方面相对于调用线程的join方法已经有了不少优化,但是CountDownLatch的计数器是一次性的,也就是等到计数器值变为0后,再调用CountDownLatch的await和countdown方法都会立刻返回,这就起不…

视图与索引连表查询内/外联和子查询

1.视图 先介绍一下视图: 从SQL的角度来看,视图和表是相同的,两者的区别在于表中存储的是实际的数据,而视图中保存的是SELECT语句(视图本身并不存储数据)。 使用视图可以轻松完成跨多表查询数据等复杂操作…

解析工会排队:动静奖励结合的魅力

每天五分钟讲解一个商业模式知识,大家好我是模式策划啊浩Zeropan_HH。 数字时代数字思想,当你还在苦恼如何让自己的商业城堡扩大时,不如放空思想来看看啊浩的文章,或许可以给你一些启发。今天的给大家分享的模式来源于《微三云赢…

Project软件使用指南:六个关键功能助力项目成功

在项目管理的复杂世界中,Project软件提供了关键的解决方案。主要功能包括:1、任务和进度管理、2、资源分配、3、财务监控、4、风险评估、5、协作增强、6、报告和洞察力。特别是在任务和进度管理方面,Project软件通过动态时间表和任务跟踪工具…

休息一会 sleep

文章目录 休息一会 sleep休息5分钟1小时后提醒我时分秒搭配使用倒计时计时器结合脚本更多信息 休息一会 sleep … note:: 莫听穿林打叶声,何妨吟啸且徐行。 苏轼 Linux sleep命令可以用来将目前动作延迟一段时间。 sleep的官方定义为: sleep - delay …

React之自定义路由组件

开篇 react router功能很强大,可以根据路径配置对应容器组件。做到组件的局部刷新,接下来我会基于react实现一个简单的路由组件。 代码 自定义路由组件 import {useEffect, useState} from "react"; import React from react // 路由配置 e…

YOLOv8 Ultralytics:使用Ultralytics框架进行定向边界框对象检测

YOLOv8 Ultralytics:使用Ultralytics框架进行定向边界框对象检测 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows 使用Ultralytics框架进行定向边界框对象检测参考文献 前言 由于本人水平有限,难免出现错漏,敬请批评改正。更多精…

Kali Linux——aircrack-ng无线教程

目录 一、准备 二、案例 1、连接usb无线网卡 2、查看网卡信息 3、开启网卡监听 4、扫描wifi信号 5、抓取握手包 6、强制断开连接 7、破解握手包 三、预防 一、准备 1、usb无线网卡(笔记本也是需要用到) 2、密码字典(Kali 系统自带…

好用便签:如何高效完成待办事项,提高工作效率?

在职场上,很多打工人总会有各种各样的待办事项需要处理,有时候因为手头上正在做的事还没做完,又接到一些其他的任务,导致不知道先做哪个,或者是忘了做某件事,导致工作效率极低。那么,如何高效处…

FineBI实战项目一(15):订单销售总额分析开发

点击新建组件,创建订单销售总额组件。 选择自定义图表,选择文本,拖拽要分析的字段到文本中。 进入仪表板,拖拽刚刚的组件进入仪表板,然后在再编辑标题。 效果如下

FineBI实战项目一(18):每小时上架商品个数分析开发

点击新建组件,创建每小时上架商品个数组件。 选择线图,拖拽cnt(总数)到纵轴,拖拽hourStr到横轴。 修改横轴和纵轴的文字。 调节连线样式。 添加组件到仪表板。

手写一个starter来理解SpringBoot的自动装配

自动装配以及简单的解析源码 自动装配是指SpringBoot在启动的时候会自动的将系统中所需要的依赖注入进Spring容器中 我们可以点开SpringBootApplication这个注解来一探究竟 点开这个注解可以发现这些 我们点开SpringBootConfiguration这个注解 可以发现实际上SpringBootApp…