算法与数据结构-跳表

文章目录

  • 什么是跳表
  • 跳表的时间复杂度
  • 跳表的空间复杂度
  • 如何高效的插入和删除
  • 跳表索引动态更新
  • 代码示例


什么是跳表

对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高,是 O(n)。
在这里插入图片描述
那怎么来提高查找效率呢?如果像图中那样,对链表建立一级“索引”,查找起来是不是就会更快一些呢?每两个结点提取一个结点到上一级,我们把抽出来的那一级叫做索引或索引层。你可以看我画的图。图中的 down 表示 down 指针,指向下一级结点。
在这里插入图片描述
如果我们现在要查找某个结点,比如 16。我们可以先在索引层遍历,当遍历到索引层中值为 13 的结点时,我们发现下一个结点是 17,那要查找的结点 16 肯定就在这两个结点之间。然后我们通过索引层结点的 down 指针,下降到原始链表这一层,继续遍历。这个时候,我们只需要再遍历 2 个结点,就可以找到值等于 16 的这个结点了。这样,原来如果要查找 16,需要遍历 10 个结点,现在只需要遍历 7 个结点。

从这个例子里,我们看出,加来一层索引之后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了。那如果我们再加一级索引呢?效率会不会提升更多呢?

跟前面建立第一级索引的方式相似,我们在第一级索引的基础之上,每两个结点就抽出一个结点到第二级索引。现在我们再来查找 16,只需要遍历 6 个结点了,需要遍历的结点数量又减少了。
在这里插入图片描述
这种链表加多级索引的结构,就是跳表。

跳表的时间复杂度

按照我们刚才讲的,每两个结点会抽出一个结点作为上一级索引的结点,那第一级索引的结点个数大约就是 n/2,第二级索引的结点个数大约就是 n/4,第三级索引的结点个数大约就是 n/8,依次类推,也就是说,第 k 级索引的结点个数是第 k-1 级索引的结点个数的 1/2,那第 k 级索引结点的个数就是 n/(2k)。

假设索引有 h 级,最高级的索引有 2 个结点。通过上面的公式,我们可以得到 n/(2h)=2,从而求得 h=log2n-1。如果包含原始链表这一层,整个跳表的高度就是 log2n。我们在跳表中查询某个数据的时候,如果每一层都要遍历 m 个结点,那在跳表中查询一个数据的时间复杂度就是 O(m*logn)。

那这个 m 的值是多少呢?按照前面这种索引结构,我们每一级索引都最多只需要遍历 3 个结点,也就是说 m=3,为什么是 3 呢?我来解释一下。

假设我们要查找的数据是 x,在第 k 级索引中,我们遍历到 y 结点之后,发现 x 大于 y,小于后面的结点 z,所以我们通过 y 的 down 指针,从第 k 级索引下降到第 k-1 级索引。在第 k-1 级索引中,y 和 z 之间只有 3 个结点(包含 y 和 z),所以,我们在 K-1 级索引中最多只需要遍历 3 个结点,依次类推,每一级索引都最多只需要遍历 3 个结点。

在这里插入图片描述

通过上面的分析,我们得到 m=3,所以在跳表中查询任意数据的时间复杂度就是 O(logn)。这个查找的时间复杂度跟二分查找是一样的。换句话说,我们其实是基于单链表实现了二分查找.

跳表的空间复杂度

通过上面的分析,我们得到 m=3,所以在跳表中查询任意数据的时间复杂度就是 O(logn)。这个查找的时间复杂度跟二分查找是一样的。换句话说,我们其实是基于单链表实现了二分查找

在这里插入图片描述
这几级索引的结点总和就是 n/2+n/4+n/8…+8+4+2=n-2。所以,跳表的空间复杂度是 O(n)。也就是说,如果将包含 n 个结点的单链表构造成跳表,我们需要额外再用接近 n 个结点的存储空间。

如何高效的插入和删除

我们知道,在单链表中,一旦定位好要插入的位置,插入结点的时间复杂度是很低的,就是 O(1)。但是,这里为了保证原始链表中数据的有序性,我们需要先找到要插入的位置,这个查找操作就会比较耗时。

对于纯粹的单链表,需要遍历每个结点,来找到插入的位置。但是,对于跳表来说,我们讲过查找某个结点的时间复杂度是 O(logn),所以这里查找某个数据应该插入的位置,方法也是类似的,时间复杂度也是 O(logn)。我画了一张图,你可以很清晰地看到插入的过程。

在这里插入图片描述

我们再看下删除操作。 如果这个结点在索引中也有出现,我们除了要删除原始链表中的结点,还要删除索引中的。因为单链表中的删除操作需要拿到要删除结点的前驱结点,然后通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点。当然,如果我们用的是双向链表,就不需要考虑这个问题了。

跳表索引动态更新

当我们不停地往跳表中插入数据时,如果我们不更新索引,就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表。
在这里插入图片描述
作为一种动态数据结构,我们需要某种手段来维护索引与原始链表大小之间的平衡,也就是说,如果链表中结点多了,索引结点就相应地增加一些,避免复杂度退化,以及查找、插入、删除操作性能下降。

当我们往跳表中插入数据的时候,我们可以选择同时将这个数据插入到部分索引层中。如何选择加入哪些索引层呢?

我们通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。

在这里插入图片描述
随机函数的选择很有讲究,从概率上来讲,能够保证跳表的索引大小和数据大小平衡性,不至于性能过度退化。

代码示例

public class SkipList {private static final float SKIPLIST_P = 0.5f;private static final int MAX_LEVEL = 16;private int levelCount = 1;private Node head = new Node();  // 带头链表public Node find(int value) {Node p = head;for (int i = levelCount - 1; i >= 0; --i) {while (p.forwards[i] != null && p.forwards[i].data < value) {p = p.forwards[i];}}if (p.forwards[0] != null && p.forwards[0].data == value) {return p.forwards[0];} else {return null;}}public void insert(int value) {// 随机索引层数int level = randomLevel();// 定义新节点Node newNode = new Node();newNode.data = value;//Node update[] = new Node[level];for (int i = 0; i < level; ++i) {update[i] = head;}// record every level largest value which smaller than insert value in update[]Node p = head;for (int i = level - 1; i >= 0; --i) {while (p.forwards[i] != null && p.forwards[i].data < value) {p = p.forwards[i];}update[i] = p;// use update save node in search path}// in search path node next node become new node forwords(next)for (int i = 0; i < level; ++i) {newNode.forwards[i] = update[i].forwards[i];update[i].forwards[i] = newNode;}// update node hightif (levelCount < level) levelCount = level;}public void delete(int value) {Node[] update = new Node[levelCount];Node p = head;for (int i = levelCount - 1; i >= 0; --i) {while (p.forwards[i] != null && p.forwards[i].data < value) {p = p.forwards[i];}update[i] = p;}if (p.forwards[0] != null && p.forwards[0].data == value) {for (int i = levelCount - 1; i >= 0; --i) {if (update[i].forwards[i] != null && update[i].forwards[i].data == value) {update[i].forwards[i] = update[i].forwards[i].forwards[i];}}}while (levelCount > 1 && head.forwards[levelCount] == null) {levelCount--;}}// 理论来讲,一级索引中元素个数应该占原始数据的 50%,二级索引中元素个数占 25%,三级索引12.5% ,一直到最顶层。// 因为这里每一层的晋升概率是 50%。对于每一个新插入的节点,都需要调用 randomLevel 生成一个合理的层数。// 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且 ://        50%的概率返回 1//        25%的概率返回 2//      12.5%的概率返回 3 ...private int randomLevel() {int level = 1;while (Math.random() < SKIPLIST_P && level < MAX_LEVEL)level += 1;return level;}public void printAll() {Node p = head;while (p.forwards[0] != null) {System.out.print(p.forwards[0] + " ");p = p.forwards[0];}System.out.println();}public class Node {private int data = -1;private Node forwards[] = new Node[MAX_LEVEL];}
}

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

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

相关文章

Makefile

什么是 Makefile 一个工程中的源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c; Makefile文件定义了一系列的规则来指定哪些文件需要先编译&#xff0c;哪些文件需要后编 译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于进行更复杂的功…

Plecs最新安装免费版下载链接/Plecs 电力仿真软件4.7.5版本下载/实测能用

Plecs最新安装免费版下载链接 Plecs 电力仿真软件4.7.5版本下载 实测能用&#xff1a;

Flutter 让软键盘不再自动弹起

1、问题说明&#xff1a; 在开发中&#xff0c;经常遇到这种事&#xff0c;一个页面有输入框&#xff0c;点击输入框后&#xff0c;会弹起软键盘&#xff0c;同时输入框会聚焦&#xff0c;手动收起软键盘后&#xff0c;点击另一个按钮前往下一个页面或者显示一个弹窗&#xff0…

web集群学习--基于CentOS构建LVS-DR集群、配置nginx负载均衡

基于CentOS构建LVS-DR集群 环境准备 主机名 ip地址 node1 192.168.1.140 client node2 192.168.1.141 LVS node3 192.168.1.142 RS1 node4 192.168.1.143 RS2配置 1.关闭防火墙和SELinux [rootclient~]# systemctl stop firewalld [rootclient~]# systemctl disabl…

数据可视化(八)堆叠图,双y轴,热力图

1.双y轴绘制 #双Y轴可视化数据分析图表 #add_subplot() dfpd.read_excel(mrbook.xlsx) x[i for i in range(1,7)] y1df[销量] y2df[rate] #用来正常显示负号 plt.rcParams[axes.unicode_minus]False figplt.figure() ax1fig.add_subplot(1,1,1)#一行一列&#xff0c;第一个区域…

机器学习笔记:李宏毅ChatGPT Finetune VS Prompt

1 两种大语言模型&#xff1a;GPT VS BERT 2 对于大语言模型的两种不同期待 2.1 “专才” 2.1.1 成为专才的好处 Is ChatGPT A Good Translator? A Preliminary Study 2023 Arxiv 箭头方向指的是从哪个方向往哪个方向翻译 表格里面的数值越大表示翻译的越好 可以发现专门做翻…

HOT79-跳跃游戏 II

leetcode原题链接&#xff1a;跳跃游戏 II 题目描述 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j &…

笔记本WIFI连接无网络【实测有效,不用重启电脑】

笔记本Wifi连接无网络实测有效解决方案 问题描述&#xff1a; 笔记本买来一段时间后&#xff0c;WIFI网络连接开机一段时间还正常连接&#xff0c;但是过一段时间显示网络连接不上&#xff0c;重启电脑太麻烦&#xff0c;选择编写重启网络脚本解决。三步解决问题。 解决方案&a…

2.4 网络安全新技术

数据参考&#xff1a;CISP官方 目录 云计算安全大数据安全移动互联网安全物联网安全工业互联网安全 一、云计算安全 1、云计算定义 云计算是指通过网络访问可扩展的、灵活的物理或虚拟共享资源池&#xff0c;并按需自助获取和管理资源的模式。在云计算中&#xff0c;计算资…

SDXL 1.0出图效果直逼Midjourney!手把手教你快速体验!

介绍 最近&#xff0c;Stability AI正式推出了全新的SDXL 1.0版本。经过我的实际测试&#xff0c;与之前的1.5版本相比&#xff0c;XL的效果有了巨大的提升&#xff0c;可以说是全方位的超越。不仅在理解提示词方面表现出色&#xff0c;而且图片的构图、颜色渲染和画面细腻程度…

一零六七、JVM梳理

JVM&#xff1f; Java虚拟机&#xff0c;可以理解为Java程序的运行环境&#xff0c;可以执行Java字节码&#xff08;Java bytecode&#xff09;并提供了内存管理、垃圾回收、线程管理等功能 java内存区域划分?每块内存中都对应什么? 方法区&#xff1a;类的结构信息、常量池、…

排序算法(九大)- C++实现

目录 基数排序 快速排序 Hoare版本&#xff08;单趟&#xff09; 快速排序优化 三数取中 小区间优化 挖坑法&#xff08;单趟&#xff09; 前后指针法&#xff08;单趟&#xff09; 非递归实现&#xff08;快排&#xff09; 归并排序 非递归实现&#xff08;归并&am…

奥威BI—数字化转型首选,以数据驱动企业发展

奥威BI系统BI方案可以迅速构建企业级大数据分析平台&#xff0c;可以将大量数据转化为直观、易于理解的图表和图形&#xff0c;推动和促进数字化转型的进程&#xff0c;帮助企业更好地了解自身的运营状况&#xff0c;及时发现问题并采取相应的措施&#xff0c;提高运营效率和质…

【数据结构】双链表

【数据结构】双链表 一. 前言二. 带头双向链表接口实现1.准备工作2. 创建一个节点 三. 初始化4. 打印5. 尾插6. 尾删7. 头插8. 头删9. 计算节点个数10. 查找数据11. 在任意位置插入数据12. 在任意位置删除数据13. 销毁 四. 如何10分钟内完成一个完整双链表 一. 前言 带头双向循…

笔记——听听前辈们的教学评一体化

精选课程内容 强而有力的知识 做中学&#xff0c;用中学&#xff0c;创中学。 这个技术很难做 关于支架的新理解 有价值 有意义 和 趣味性 权衡&#xff0c;不能为了趣味性舍弃价值 举例说明文 被教成了文学作品 导致所教所学 悄然发生了偏移。 所以教学评如何一直&#xff…

Ajax 笔记(一)

笔记目录 1. Ajax 入门1.1 Ajax 概念1.2 axios 使用1.2.1 URL1.2.2 URL 查询参数1.2.3 小案例-查询地区列表1.2.4 常用请求方法和数据提交1.2.5 错误处理 1.3 HTTP 协议1.3.1 请求报文1.3.2 响应报文 1.4 接口文档1.5 案例1.5.1 用户登录&#xff08;主要业务&#xff09;1.5.2…

如何构造一个安全的单例?

为什么要问这个问题&#xff1f; 我们知道&#xff0c;单例是一种很常用的设计模式&#xff0c;主要作用就是节省系统资源&#xff0c;让对象在服务器中只有一份。但是实际开发中可能有很多人压根没有写过单例这种模式&#xff0c;只是看过或者为了面试去写写demo熟悉一下。那…

职责链模式(C++)

定义 使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递请求&#xff0c;直到有一个对象处理它为止。 应用场景 在软件构建过程中&#xff0c;一个请求可能被多个对象处理&#xff0c;…

Redis单机,主从,哨兵,集群四大模式

Redis 单机模式 Redis 单机模式是指 Redis 数据库在单个服务器上以独立的、单一的进程运行的模式。在这种模式下&#xff0c;Redis 不涉及数据分片或集群配置&#xff0c;所有的数据和操作都在一个实例中进行。以下是关于 Redis 单机模式的详细介绍&#xff1a; 单一实例&#…

如何搭建自动化测试框架?资深测试整理的PO模式,一套打通自动化...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Po模型介绍 1、简…