链表(4) ----跳表

news2024/9/20 9:34:03

跳表(Skip List)是一种随机化的数据结构,用于替代平衡树(如 AVL 树或红黑树)。它是基于多层链表的,每一层都是上一层的子集。跳表可以提供与平衡树相似的搜索性能,即在最坏情况下,搜索、插入和删除操作都可以在 O(log n) 的时间复杂度内完成。

跳表的基本组成:

  1. 多层链表:跳表由若干层链表组成,每一层都是下面一层的“快速通道”。最底层是基础链表,包含所有的元素。
  2. 索引节点:每一层的链表都包含一些索引节点,这些节点指向下一层的某些节点。
  3. 随机化:每个节点都有相同的概率(通常为 1/2)决定是否向上增加一层,从而创建一个索引节点。

跳表的操作:

  • 搜索:在跳表中搜索元素时,从顶层开始,逐层向下,直到找到元素或到达底层。在每一层,通过索引节点快速跳过多个节点。
  • 插入:插入操作首先在底层进行标准的链表插入。然后,根据随机化过程决定是否在更高层创建索引节点。
  • 删除:删除操作首先在所有包含该元素的层上找到它,然后逐层删除。如果某个层的索引节点在删除操作后变得无效(即前后节点相同),则该索引节点也会被删除。

跳表的优点:

  • 简单性:跳表的实现相对简单,不需要复杂的旋转操作,如平衡树所需的。
  • 性能:跳表提供了与平衡树相似的搜索性能,且在某些情况下,由于其随机化的特性,可能具有更好的性能。
  • 并发操作:跳表适合进行并发操作,因为它的插入和删除操作不需要像平衡树那样进行大量的结构调整。

跳表的应用:

跳表在许多场景下都有应用,尤其是在需要快速搜索、插入和删除操作的数据库和索引系统中。例如,Redis 这个流行的键值存储数据库就使用了跳表来实现有序集合。

跳表是一种非常实用的数据结构,它结合了链表的简单性和平衡树的高效搜索性能。

算法设计:

跳表的概念 

链表的优点 

跳表的设计 

跳表中 的前驱 

跳表的添加 

跳表的删除 

力扣1206  ---跳表 

不使用任何库函数,设计一个 跳表 。

跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。

例如,一个跳表包含 [30, 40, 50, 60, 70, 90] ,然后增加 8045 到跳表中,以下图的方式操作:

跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)

了解更多 : 跳表 - OI Wiki

在本题中,你的设计应该要包含这些函数:

  • bool search(int target) : 返回target是否存在于跳表中。
  • void add(int num): 插入一个元素到跳表。
  • bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。

注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。

示例 1:

输入
["Skiplist", "add", "add", "add", "search", "add", "search", "erase", "erase", "search"]
[[], [1], [2], [3], [0], [4], [1], [0], [1], [1]]
输出
[null, null, null, null, false, null, true, false, true, false]

解释
Skiplist skiplist = new Skiplist();
skiplist.add(1);
skiplist.add(2);
skiplist.add(3);
skiplist.search(0);   // 返回 false
skiplist.add(4);
skiplist.search(1);   // 返回 true
skiplist.erase(0);    // 返回 false,0 不在跳表中
skiplist.erase(1);    // 返回 true
skiplist.search(1);   // 返回 false,1 已被擦除

提示:

  • 0 <= num, target <= 2 * 104
  • 调用searchadd,  erase操作次数不大于 5 * 104 

代码 

MAX_LEVEL = 32
P_FACTOR = 0.5

def random_level() -> int:
    lv = 1
    while lv < MAX_LEVEL and random.random() < P_FACTOR:
        lv += 1
    return lv

class SkiplistNode:
    __slots__ = 'val', 'forward'

    def __init__(self, val: int, max_level=MAX_LEVEL):
        self.val = val
        self.forward = [None] * max_level

class Skiplist:
    def __init__(self):
        self.head = SkiplistNode(-1)
        self.level = 0

    def search(self, target: int) -> bool:
        curr = self.head
        for i in range(self.level - 1, -1, -1):
            # 找到第 i 层小于且最接近 target 的元素
            while curr.forward[i] and curr.forward[i].val < target:
                curr = curr.forward[i]
        curr = curr.forward[0]
        # 检测当前元素的值是否等于 target
        return curr is not None and curr.val == target

    def add(self, num: int) -> None:
        update = [self.head] * MAX_LEVEL
        curr = self.head
        for i in range(self.level - 1, -1, -1):
            # 找到第 i 层小于且最接近 num 的元素
            while curr.forward[i] and curr.forward[i].val < num:
                curr = curr.forward[i]
            update[i] = curr
        lv = random_level()
        self.level = max(self.level, lv)
        new_node = SkiplistNode(num, lv)
        for i in range(lv):
            # 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点
            new_node.forward[i] = update[i].forward[i]
            update[i].forward[i] = new_node

    def erase(self, num: int) -> bool:
        update = [None] * MAX_LEVEL
        curr = self.head
        for i in range(self.level - 1, -1, -1):
            # 找到第 i 层小于且最接近 num 的元素
            while curr.forward[i] and curr.forward[i].val < num:
                curr = curr.forward[i]
            update[i] = curr
        curr = curr.forward[0]
        if curr is None or curr.val != num:  # 值不存在
            return False
        for i in range(self.level):
            if update[i].forward[i] != curr:
                break
            # 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳
            update[i].forward[i] = curr.forward[i]
        # 更新当前的 level
        while self.level > 1 and self.head.forward[self.level - 1] is None:
            self.level -= 1
        return True

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

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

相关文章

JMX 反序列化漏洞

前言 前段时间看到普元 EOS Platform 爆了这个洞&#xff0c;Apache James&#xff0c;Kafka-UI 都爆了这几个洞&#xff0c;所以决定系统来学习一下这个漏洞点。 JMX 基础 JMX 前置知识 JMX&#xff08;Java Management Extensions&#xff0c;即 Java 管理扩展&#xff0…

verilog基础语法入门

文章目录 前言一、模块定义1. 模块声明2. 端口定义3. 信号类型声明4. 逻辑功能定义 二、运算符与表达式1. 算术运算符2. 逻辑运算符3. 位运算符4. 关系运算符5. 等式运算符6. 缩减运算符7. 移位运算符8. 条件运算符9. 位拼接运算符 三、语句1. 赋值语句2. 块语句3. 条件语句4. …

第1关 -- Linux 基础知识

闯关任务 完成SSH连接与端口映射并运行hello_world.py ​​​​ 可选任务 1 将Linux基础命令在开发机上完成一遍 可选任务 2 使用 VSCODE 远程连接开发机并创建一个conda环境 创建新的虚拟环境lm3 可选任务 3 创建并运行test.sh文件 参考文档 文档&#xff1a;https://g…

tcp协议下的socket函数

目录 1.socket函数 2.地址转换函数 1.字符串转in_addr的函数:​编辑 2.in_addr转字符串的函数&#xff1a;​编辑 1.关于inet_ntoa函数 3.listen函数 4.简单的Server模型 1.初步模型 1.sock函数和accept函数返回值的sockfd的区别 2.运行结果和127.0.0.1的意义 2.单进…

如何设计数据中心100G网络光纤布线

随着全球企业对带宽的需求呈指数级增长&#xff0c;数据中心需要升级以增强其计算、存储和网络能力。数据中心从10G/25G向100G迁移成为必然趋势。随着网络升级&#xff0c;数据中心的光纤布线系统也需要随之优化。本文将指导您如何设计数据中心100G网络光纤布线。 100G以太网的…

【LSTM和GRU极简,和最新的TT也就是状态】机器学习模型来学习状态

LSTM&#xff08;长短期记忆网络&#xff09;中的关键参数包括输入门、遗忘门、输出门、细胞状态和隐藏状态。以下是如何进行推理计算的示例&#xff1a; LSTM参数和公式 输入门&#xff08;i_t&#xff09;&#xff1a;决定输入的信息量。 遗忘门&#xff08;f_t&#xff0…

路网双线合并单线——ArcGIS 解决方法

路网双线合并成单线是一个在地图制作、交通规划以及GIS分析中常见的需求。双线路网定义&#xff1a;具有不同流向、不同平面结构的道路。此外&#xff0c;车道数较多的道路&#xff08;例如&#xff0c;双黄实线车道数大于4的道路&#xff09;也可以视为双线路网&#xff0c;本…

appium自动化测试报错。

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

sip-URI的基本结构

sip-URI&#xff08;sip Uniform Resource Identifier&#xff1a;会话初始协议统一资源标识符&#xff09;的一般格式&#xff1a; sip:[userinfo]host[:port][;transporttcp|udp|tls|sctp][?parameters] sip: 协议标识符&#xff0c;表示这是一个SIP URI。userinfo&#x…

智慧煤矿:AI视频智能监管解决方案引领行业新变革

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到各个行业&#xff0c;为传统产业的转型升级提供了强大的动力。在煤矿行业中&#xff0c;安全监管一直是一个重要的议题。为了提高煤矿的安全生产水平&#xff0c;降低事故发生率&#xff0c;智…

pytorch学习(八)Dataset加载分类数据集

我们之前用torchvision加载了pytorch的网络数据集&#xff0c;现在我们用Dataset加载自己的数据集&#xff0c;并且使用DataLoader做成训练数据集。 图像是从网上下载的&#xff0c;网址是 点这里&#xff0c;标签是图像文件夹名字。下载完成后作为自己的数据集。 1.加载自己…

基于纹理和统计图像特征集成的计算机辅助乳腺癌检测

诊断通常使用组织病理学切片&#xff0c;可以确定组织是否处于导管原位癌(DCIS)阶段&#xff0c;其中癌细胞尚未扩散到周围乳腺组织&#xff0c;或浸润性导管癌(IDC)阶段&#xff0c;其中细胞已渗透到邻近组织。对于医生来说&#xff0c;检测IDC非常耗时且具有挑战性。因此&…

搜集日志。

logstash 负责&#xff1a; 接收数据 input — 解析过滤并转换数据 filter(此插件可选) — 输出数据 output input — decode — filter — encode — output elasticsearch 查询和保存数据 Elasticsearch 去中心化集群 Data node 消耗大量 CPU、内存和 I/O 资源 分担一部分…

分布式搜索引擎ES-elasticsearch入门

1.分布式搜索引擎&#xff1a;luceneVS Solr VS Elasticsearch 什么是分布式搜索引擎 搜索引擎&#xff1a;数据源&#xff1a;数据库或者爬虫资源 分布式存储与搜索&#xff1a;多个节点组成的服务&#xff0c;提高扩展性(扩展成集群) 使用搜索引擎为搜索提供服务。可以从海量…

Linux下docker快速安装gitea

之前在服务器上装的gitlab来管理个人项目&#xff0c;但是gitlab服务启动后能明显感受到占用资源比较严重。最近服务器到期&#xff0c;换了个服务器还没来得及装gitlab&#xff0c;刚好最近接触到gitea&#xff0c;网上是这么说的 占用资源少&#xff0c;适合个人开发者&…

【数据结构】:时间和空间复杂度在这篇里面一点都不复杂

目录 如何衡量一个代码的好坏 时间复杂度 概念 计算方法 实例计算 【实例1】 【实例2】 【实例3】 【实例4】&#xff1a;冒泡排序的时间复杂度 【实例5】&#xff1a;二分查找的时间复杂度 【实例6】&#xff1a;阶乘递归的时间复杂度 【实例7】&#xff1a;斐波那契…

P3-AI产品经理-九五小庞

AI产品的数据流向 美团外卖&#xff0c;实时只能调度 美团28分钟送达需求的分析 AI产品常用的算法 常用算法 常见的AI算法解析 自然语言生成NLG语音识别&#xff1a;科大讯飞&#xff0c;通义千问 虚拟现实机器学习平台 决策管理系统生物特征识别技术 RPA(机器人流程自动…

百日筑基第二十五天-java开发程序员常犯的错总结

百日筑基第二十五天-java开发程序员常犯的错 一、将数组转换为ArrayList 要将数组转换为ArrayList&#xff0c;开发人员通常会这样做&#xff1a; List<String> list Arrays.asList(arr);**Arrays.asList()将返回 ArrayList私有静态类的 Arrays&#xff0c;而不是 ja…

《数据结构:栈和队列》

文章目录 一、栈1、概念与结构 二、栈的实现1、栈的结构和功能2、初始化栈3、入栈4、出栈5、判断栈是否为空6、取栈元素和栈有效个数7、销毁栈 三、队列1、概念与结构 四、队列的实现1、队列的实现结构和功能2、队列初始化3、入队列4、判断队列是否为空5、出队列6、取队头/队尾…

WPF+Mvvm 项目入门完整教程(一)

WPF+Mvvm 入门完整教程一 创建项目MvvmLight框架安装完善整个项目的目录结构创建自定义的字体资源下载更新和使用字体资源创建项目 打开VS2022,点击创建新项目,选择**WPF应用(.NET Framework)** 创建一个名称为 CommonProject_DeskTop 的项目,如下图所示:MvvmLight框架安装…