深入理解数据结构 —— 跳表

news2024/11/18 17:47:07

什么是跳表

只要是平衡搜索二叉树能实现的功能,跳表都能实现,且时间复杂度都相同

例如:

  • 哈希表的功能:插入,查找,删除
  • 有序表的功能:查找大于某值最小的数,小于某值最大的数,按顺序遍历

这些操作的时间复杂度都为O(logN)

跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能

整体结构

跳表由一个个节点组成,节点之间按照顺序连接,最左边是头结点

每个节点是一个多层的结构,除了记录值val外,还用nexts记录层级信息,每一层的指针指向下一个节点

我们设计的跳表支持重复插入,需要用count记录数量

static  class SkipNode {
    int val;
    int count;
    private List<SkipNode> nexts;

    public SkipNode(int val) {
        this.val = val;
        this.count = 1;
        this.nexts = new ArrayList<>();
    }
}

至于有多少层,完全根据概率决定,层级越高,概率越低。因此跳表中需要维护一个概率rate

同时需要记录最左边的头结点head,和最大的层数maxLevel

static  class SkipMap {

    private  double rate;
    private SkipNode head;
    private  int maxLevel;

    public SkipMap(double rate) {
        this.rate = rate;
        this.head = new SkipNode(0);
        // 初始化第0层
        this.head.nexts.add(null);
        this.maxLevel = 0;
    }

每个节点按照一定的顺序从左往右排列,相同的层级会用指针串联起来

例如下图的跳表:节点0,3只有一层,节点2,4有至少两层,因此head中level等于1的指针会直接连向节点2,再从2连向节点4

在这里插入图片描述

查找

流程

要查找跳表中是否有元素key:

  1. 从head节点maxLevel层开始找,找每一层小于key的最后一个节点,记为cur
  • 因为是从head开始找,我们假设head的节点值为无限小,那么在每一层一定能找到最后一个小于key的节点
  1. 然后从cur的下一层开始寻找,重复步骤1
  2. 直到找到最后一层节点为止

例如,要在如下的跳表中查看是否有元素3:

  • 从head节点的第maxLevel层,也就是第3层开始找,其下一个节点值为4,大于等于3,因此从第2层开始找
  • 第2层的下一个节点还是4,继续降低层级
  • 第1层的下一个节点为2,比3小,跳到该节点,继续看该节点的下一个节点为4,大于等于3,降低层级
  • 第0层的下一个节点为3,就等于参数key,返回下一个节点

整个流程如下图所示:

在这里插入图片描述

怎么判断跳表中是否存在元素key?

因为跳表中的节点一层存在第0层,现在找到了第0层中,小于key最右边的的节点cur

如果key存在,那么cur右边的节点的值一定为key

因此判断cur.nexts.get(0).val== key就好了

正确性证明

这种做法有没有在可能查找的路径中漏掉一些元素,导致跳表中客观上有该元素,却返回没找到?

根据概率,层级越高的节点越稀疏,因此从高层开始查找,一次可能跳过很多节点

示例中,当路径从head到节点2时,跳过了节点0,节点2小于key,由于跳表中的节点按照顺序从左到右排列,那些被跳过的节点一定也小于key,而又在节点2的左边,不可能成为小于key且最右边的节点,因此跳过这些节点没有问题

当cur的下一个节点的值大于等于key时,不能再往右跳了,需要在当前节点降低层级查找

下一层级可能有更多的节点,next连到了小于key的节点,又能继续跳

由于只排除了不可能成为答案的节点,因此正确性没有问题

时间复杂度

当数据量较大时,概率分布均匀,平均每层跳过剩余节点的一半的节点,因此时间复杂度为O(logN)

代码实现

在level层里找小于key最右的节点:

// level:在哪一层找
// cur:从哪个节点开始找
// key:需要小于的值
public SkipNode mostRightLessKeyInLevel(int level, SkipNode cur,int key) {
    SkipNode next = cur.nexts.get(level);
    while (next != null && next.val < key) {
        cur = next;
        next = next.nexts.get(level);
    }
    return cur;
}

查找:

public SkipNode find(int key) {
    int level = maxLevel;
    SkipNode cur = head;
    while (level >= 0) {
        cur = mostRightLessKeyInLevel(level,cur,key);
        level--;
    }
    if (cur.nexts.get(0) != null && cur.nexts.get(0).val == key) {
        return cur.nexts.get(0);
    }
    return  null;
}

插入

往跳表中插入节点的步骤如下:

  • 随机决定层数newLevel

    • 生成0~1的随机数,如果小于rate,就增加层数,直到大于rate位置
    • 需要控制跳表的平均层级时,可以将rate调小,一般使用可以设置为0.5,redis设置为0.25,因此redis的跳表平均层数很低
  • 如果maxLevel小于newLevel,将head的层级增加到newLevel

  • 新建待插入节点newNode,初始化有newLevel个层级

  • 从head节点,maxLevel层开始

    • 每一层找比key小,最右的节点,将newNode串到该节点后面
    • 去下一层级找,不断重复这个过程,直到第0层串完为止

例如:往下图的跳表中插入key为4,newLevel为6的节点:

在这里插入图片描述

代码实现如下:

public  void put(int key) {
    SkipNode existNode = find(key);
    if (existNode != null) {
        existNode.count++;
        return;
    }

    size++;
    // 随机决定层数
    int newLevel = 0;
    while (Math.random() <= this.rate) {
        newLevel++;
    }
    // 将head增加到和newLevel一样的层数
    while (maxLevel < newLevel) {
        head.nexts.add(null);
        maxLevel++;
    }
    
    // 新建待插入节点newNode,初始化有newLevel个层级
    SkipNode newNode = new SkipNode(key);
    for (int i = 0;i <= newLevel;i++) {
        newNode.nexts.add(null);
    }

    int level = maxLevel;
    SkipNode pre = head;
    while (level >= 0) {
        // 找到当前层,比key小的最右的节点
        pre = mostRightLessKeyInLevel(level, pre, key);
        // 只有降到newLevel层才将newNode串到跳表中
        if (level <= newLevel) {
            // 将newNode串到pre的后面
            newNode.nexts.set(level, pre.nexts.get(level));
            pre.nexts.set(level, newNode);
        }
        level--;
    }
}

删除

删除的逻辑和插入类似,从head节点的maxLevel层开始找:

  • 每一层找比key小,最右的节点,将该节点的next删除
  • 降低层级,去下一层找到并删除,直到删完所有层级的数据为止

代码实现如下:

public  void remove(int key) {
    SkipNode existNode = find(key);
    if (existNode != null) {
        existNode.count--;
        if (existNode.count != 0) {
            return;
        }
    }

    size--;
    int level = maxLevel;
    SkipNode cur = head;
    while (level >= 0) {
        cur = mostRightLessKeyInLevel(level,cur,key);
        SkipNode next = cur.nexts.get(level);
        if (next != null && next.val == key) {
            cur.nexts.set(level, next.nexts.get(level));
        }
        level--;
    }
}

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

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

相关文章

干货 | 数据跨境传输合规体系的构建思路

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;研究背景随着经济活动数字化转型加快&#xff0c;“数据”对生产、流通、分配和消费活动产生重要影响&#xff0c;成为新的生产要素。地区之间数据流通愈发频繁&#xff0c;…

图像频域滤波(理想低通滤波)

图像变换是对图像信息进行变换&#xff0c;是能量保持但重新分配&#xff0c;利于加工处理。这里主要介绍傅里叶变换的图像频域滤波。 图像从空间域变换到频域后&#xff0c;其低频分量对应图像中灰度值变化较为缓慢的区域&#xff0c;高频分量表征图像中物体的边缘和随机噪声等…

基于yolov5的钢材表面缺陷识别(pycharm连接远程服务器,老版本yolov5运行遇到的问题)

时间&#xff1a;2023年1月 1 pycharm远程连接服务器 提示&#xff1a;需要下载pycharm专业版。 参考文献&#xff1a; [1] [2] [3] [4] 设置解释器的界面有一些不同&#xff0c;在此截图记录一下。 &#xff08;这是已经弄好了之后回头截图的&#xff0c;假设它不存在哈) …

【寒假每日一题】洛谷 P6206 [USACO06OCT] Another Cow Number Game G

题目链接&#xff1a;P6206 [USACO06OCT] Another Cow Number Game G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 奶牛们在玩一种数字游戏&#xff0c;Bessie 想让你帮她预测一下结果。游戏开始时&#xff0c;Bessie 将得到一个正整数 N。此时她的分数为 0。 奶…

C 程序设计教程(07)—— 数据类型转换

C 程序设计教程&#xff08;07&#xff09;—— 数据类型转换 该专栏主要介绍 C 语言的基本语法&#xff0c;作为《程序设计语言》课程的课件与参考资料&#xff0c;用于《程序设计语言》课程的教学&#xff0c;供入门级用户阅读。 目录C 程序设计教程&#xff08;07&#xff…

mysql学习总结(一)

总结一下近期学习的mysql内容&#xff1a;这里主要总结一下mysql的底层数据结构索引的本质是什么&#xff1f;索引的本质就是排好序的一种数据结构&#xff0c;通过索引我们能干什么呢&#xff1f;&#xff0c;快速的去定位到我们想要查找的数据&#xff0c;就像是你看书&#…

Ansible 介绍与实战操作演示

文章目录一、概述二、Ansible 架构三、Ansible 工作原理四、Ansible 安装与基础配置1&#xff09;开启记录日志2&#xff09;去掉第一次连接ssh ask确认五、Ansible 的七个命令1&#xff09;ansible2&#xff09;ansible-doc3&#xff09;ansible-playbook4&#xff09;ansible…

非线性系统辨识:非线性 ARX 和 Hammerstein-Wiener

1. 系统辨识 系统辨识是根据系统的输入输出时间函数来确定描述系统行为的数学模型。现代控制理论中的一个分支。通过辨识建立数学模型的目的是估计表征系统行为的重要参数&#xff0c;建立一个能模仿真实系统行为的模型&#xff0c;用当前可测量的系统的输入和输出预测系统输出…

Js逆向教程25-BOM DOM过检测

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; Js逆向教程25-BOM DOM过检测 一、JS BOM 检测 它是一种浏览器环境&#xff0c;脱离了浏览器在外部不能直接调用的就是BOM 在浏览器中…

SAP工作流对象类

目录 1. 实现IF_WORKFLOW接口 2. 创建流程属性 3. 接口方法参考 4. 定义事件 5. 工作流触发&#xff08;事件抛出&#xff09; 业务对象作为工作流的数据内核&#xff0c;也是联系业务流程和工作流的重要核心&#xff0c;体现形式一般为BOR或者业务对象类&#xff0c;用来标识不…

SpringCloud从入门到精通(六)

Hystrix-熔断器 Hystrix-概述 • Hystix 是Netflix 开源的一个延迟和容错库&#xff0c;用于隔离访问远程服务、第三方库&#xff0c;防止出现级联失败&#xff08;雪崩&#xff09;。• 雪崩&#xff1a;一个服务失败&#xff0c;导致整条链路的服务都失败的情形 Hystix 主要功…

【Neo4j构建知识图谱】:cypher操作语言加载 CSV电影人数据集链接文件

这目录 数据链接来源1、创建约束2、从 CSV 文件添加节点3、从 CSV 文件添加关系4、运行cypher查询5、清理数据库参考CSV 文件可以使用LOAD CSV密码条款。出于安全原因,无法加载本地CSV文件,这些文件必须在HTTP或HTTPS服务器(如GitHub,Google Drive和Dropbox)上公开访问。使…

Python 中将列表中的每个元素除以一个数字

Python 中将列表中的每个元素除以一个数字&#xff1a; 使用列表理解来遍历列表。在每次迭代中&#xff0c;将当前列表元素除以数字。新列表将包含除法结果。 my_list [8, 12, 20]# ✅ divide each element in list by number new_list [item / 2 for item in my_list] pri…

雪花算法笔记

SnowFlake 雪花算法 SnowFlake 中文意思为雪花&#xff0c;故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本。 实现原理 雪花算法原理就是生成一个的64位比特位的 long 类型的唯一 id。 最高1位固定值0&#xff0c;因…

React Context 完美替代品 Jotai

1. 前言 React 的属性透传场景 虽然有很多方式可以实现&#xff0c;但能做到代码写的少、re-render 轻松处理的方式并不多。 而状态管理工具 Jotai 却可以很好的解决这些问题。 最近的业务和组件场景里 也在用此方式实现。 2. React Context 的不足 常规解决数据透传通常使…

BUUCTF 之 [ACTF2020 新生赛]Exec(命令执行漏洞)

BUUCTF 之 [ACTF2020 新生赛]Exec&#xff08;命令执行漏洞&#xff09;相关观察进攻相关 项目内容难度简单类型WEB靶场BUUCTF坐标Exec观察 这界面和这网页标题结合起来&#xff0c;相信给位都能猜到这个靶场中很有可能存在命令执行漏洞。 进攻 构造如下语句显示当前路径中的…

Learning Monocular Visual Odometry via Self-Supervised Long-Term Modeling

Paper name Learning Monocular Visual Odometry via Self-Supervised Long-Term Modeling Paper Reading Note URL: https://arxiv.org/pdf/2007.10983.pdf TL;DR ECCV 2020 文章&#xff0c;该文章认为在短时间序列上训练无法在长时间序列上良好泛华&#xff0c;所以受到…

从Web3视角审视茅台的“元宇宙”APP,这或是中国版的“星巴克奥德赛”

图片来源&#xff1a;由无界 AI 绘画工具生成2023年1月1日&#xff0c;一款名为《巽风数字世界》的APP登录App Store&#xff0c;这是由茅台和网易联合推出的虚拟世界APP。因而&#xff0c;有媒体称&#xff0c;茅台要进军元宇宙了&#xff01;简单讲&#xff0c;这是一款虚拟世…

Spring核心与设计思想 -- IoC与DI

Spring核心与设计思想 -- IoC与DI一、Spring 是什么&#xff1f;1.1 什么是容器&#xff1f;1.2 什么是 IoC&#xff1f;二、理解 IoC2.1 传统程序开发的问题2.2 分析2.3 控制反转式程序开发2.4 对比总结规律2.5 理解 Spring IoC三、DI 概念说明一、Spring 是什么&#xff1f; …

k8s集群部署springboot项目

一、前言 本篇,我们将基于k8s集群,模拟一个比较接近实际业务的使用场景,使用k8s集群部署一个springboot的项目,我们的需求是: 部署SpringBoot项目到阿里云服务器 ;基于容器打包,推送私有镜像仓库 ;采用K8S集群部署,对外暴露服务,pod副本扩容,公网可以访问 ;二、完…