C++【跳表】

news2024/12/23 13:01:52

文章目录

  • 一、什么是跳表
  • 二、跳表的实现
  • 三、跳表性能分析

一、什么是跳表

skiplist本质上也是一种查找结构,用于解决算法中的查找问题,跟平衡搜索树和哈希表的价值是一样的,可以作为key或者key/value的查找模型。

skiplist是由William Pugh发明的,最早出现于他在1990年发表的论文《Skip Lists: A
Probabilistic Alternative to Balanced Trees》。对细节感兴趣的同学可以下载论文原文来阅读。

skiplist,顾名思义,首先它是一个list。实际上,它是在有序链表的基础上发展起来的。如果是一个有序的链表,查找数据的时间复杂度是O(N)。

William Pugh开始的优化思路:

  1. 假如我们每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点,如下图b所
    示。这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半。由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。
  2. 以此类推,我们可以在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一个指针,从而产生第三层链表。如下图c,这样搜索效率就进一步提高了。
  3. skiplist正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似二分查找,使得查找的时间复杂度可以降低到O(log n)。但是这个结构在插入删除数据的时候有很大的问题,插入或者删除一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。
    在这里插入图片描述
  4. skiplist的设计为了避免这种问题,做了一个大胆的处理,不再严格要求对应比例关系,而是插入一个节点的时候随机出一个层数。这样每次插入和删除都不需要考虑其他节点的层数, 这样就好处理多了。细节过程入下图:

在这里插入图片描述

对于上图中的c,假设查找19
1、比9大,向右走,跳跃到9
2、比21小,向下走
3、比17大,向右走,跳跃17
4、比21小,向下走
5、跟19相等,找到了

那一个结点到底应该给几层?
随机出1000我就给1000?不太可能吧。
一般跳表会设计一个最大层数maxLevel的限制,其次会设置一个多增加一层的概率p。那么计算这个随机层数的伪代码如下图:
在这里插入图片描述
我们需要创建一个随机函数生成[0 ,1)之间的数据。
如果我们假设p=0.25(25%的概率会增加一层)
那么创建一层的概率:1-p=0.75
两层:(1-p)×p
三层:(1-p)2 ×p

一个节点的平均层数(也即包含的平均指针数目),计算如下:
在这里插入图片描述

当p=1/2时,每个节点所包含的平均指针数目为2;
当p=1/4时,每个节点所包含的平均指针数目为1.33。

二、跳表的实现

跳表leetcode

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

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

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

在这里插入图片描述

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

了解更多 : https://en.wikipedia.org/wiki/Skip_list

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

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
调用search, add, erase操作次数不大于 5 * 104
通过次数27,426提交次数39,687

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/design-skiplist
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

struct SkiplistNode
{
    int _val;
    //用来存next指针的vector
    vector<SkiplistNode*> _nextV;
    SkiplistNode(int val,int level)
        :_val(val)
        ,_nextV(level,nullptr)
    {}
};
class Skiplist {
    typedef SkiplistNode Node;
public:
    Skiplist() {
        srand(time(0));
        //头结点,层数是1
        _head=new Node(-1,1);
    }
    
    vector<Node*> FindPrevNode(int num)
	{
        //创建一个临时节点先指向我们的头节点
		Node* cur = _head;
        //初始化我们的层数是我们头结点的层数
		int level = _head->_nextV.size() - 1;

		// 插入位置每一层前一个节点指针
		vector<Node*> prevV(level + 1, _head);

        //当我们还没有走到最底下那一层的时候
		while (level >= 0)
		{
			// 目标值比下一个节点值要大,向右走
			// 下一个节点是空(尾),目标值比下一个节点值要小,向下走
			if (cur->_nextV[level] && cur->_nextV[level]->_val < num)
			{
				// 向右走
				cur = cur->_nextV[level];
			}
            //如果当前层没有下一个节点了
            //或者当前层的下一个节点比我们的目标值小
			else if (cur->_nextV[level] == nullptr
				|| cur->_nextV[level]->_val >= num)
			{
                //只有当需要转移到下一层的时候,我们才找到了当前层的前置结点
				// 更新level层前一个
				prevV[level] = cur;

				// 向下走
				--level;
			}
		}

		return prevV;
	}
    //查找数据
    bool search(int target) {
        Node* cur=_head;
        //往最高层走
        int level=_head->_nextV.size()-1;
        while(level>=0)
        {
            //目标值比下一个结点值要大,向右走
            //下一个节点是空(尾),目标值比下一个结点值要小,向下走
            if(cur->_nextV[level]&&cur->_nextV[level]->_val<target)
            {
                //向右走
                cur=cur->_nextV[level];
            }
            else if(cur->_nextV[level]==nullptr || cur->_nextV[level]->_val>target)
            {
                //向下走
                --level;
            }else{
                return true;
            }
        }

        return false;
    }
    
    void add(int num) {
        vector<Node*> prev=FindPrevNode(num);
        //产生新节点的层数
        int n=RandomLevel();
        //数据是num,有n层的结点
        Node* newnode=new Node(num,n);
        //如果n超过了当前最大的层数,那就升高一下head的层数
        if(n>_head->_nextV.size())
        {
            //将头结点的next数组的大小开辟到我们当前的最大的层数大小,新的部分用nullptr填补
             _head->_nextV.resize(n,nullptr);
            //将我们的前置结点也同样开辟到n的大小
            prev.resize(n,_head);
        }
           

        //链接前后节点
        //每一层我们都是要更新的
        for(size_t i=0;i<n;++i)
        {
            newnode->_nextV[i]=prev[i]->_nextV[i];
            prev[i]->_nextV[i]=newnode;
        }
    }
    
    bool erase(int num) {
        vector<Node*> prev=FindPrevNode(num);
        //第一层下一个不是val,或者val不在表中
        if (prev[0]->_nextV[0]==nullptr ||prev[0]->_nextV[0]->_val!=num)
        {
            return false;
        }
        else{
            Node* del =prev[0]->_nextV[0];
            //del结点每一层的前后指针链接起来
            for(size_t i=0;i<del->_nextV.size();i++)
            {
                prev[i]->_nextV[i]=del->_nextV[i];
            }
            delete del;

            // 如果删除最高层节点,把头节点的层数也降一下
			int i = _head->_nextV.size() - 1;
			while (i >= 0)
			{
				if (_head->_nextV[i] == nullptr)
					--i;
				else
					break;
			}
			_head->_nextV.resize(i + 1);
            return true;
        }
    }

    int RandomLevel()
    {
        size_t level=1;

        // rand()->[0,RAND_MAX]之间
        while(rand()<RAND_MAX*_p&&level<_maxLevel)
        {
            ++level;
        }
        return level;
    }
private:
    Node* _head;//头结点
    size_t _maxLevel=32;//最大层数
    double _p=0.5;//创建新层的概率
};

/**
 * Your Skiplist object will be instantiated and called as such:
 * Skiplist* obj = new Skiplist();
 * bool param_1 = obj->search(target);
 * obj->add(num);
 * bool param_3 = obj->erase(num);
 */

三、跳表性能分析

  1. skiplist相比平衡搜索树(AVL树和红黑树)对比,都可以做到遍历数据有序,时间复杂度也差不多。skiplist的优势是:a、skiplist实现简单,容易控制。平衡树增删查改遍历都更复杂。b、skiplist的额外空间消耗更低。平衡树节点存储每个值有三叉链,平衡因子/颜色等消耗。skiplist中p=1/2时,每个节点所包含的平均指针数目为2;skiplist中p=1/4时,每个节点所包含的平均指针数目为1.33;
  2. skiplist相比哈希表而言,就没有那么大的优势了。相比而言a、哈希表平均时间复杂度是O(1),比skiplist快。b、哈希表空间消耗略多一点。skiplist优势如下:a、遍历数据有序b、skiplist空间消耗略小一点,哈希表存在链接指针和表空间消耗。c、哈希表扩容有性能损耗。d、哈希表再极端场景下哈希冲突高,效率下降厉害,需要红黑树补足接力

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

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

相关文章

rabbitmq镜像模式

rabbitmq集群模式分为两种&#xff1a;普通模式和镜像模式 如果不设置集群模式则为普通模式&#xff0c;下面是将集群修改为镜像模式 镜像集群 RabbitMQ镜像功能&#xff0c;需要基于RabbitMQ策略来实现&#xff0c;策略policy是用来控制和修改群集范围的某个vhost的队列行为…

STM32/51单片机实训day8——基于Keil5+Proteus8使用DHT11温度传感器实现温湿度采集并在LM016L液晶屏上显示

目录 任务指导 1 DHT11产品概述 2 应用领域 3 传感器性能说明 4 接口说明 5 电源引脚 6 串行接口(单线双向) 通讯过程如图3所示&#xff1a; 任务实现​​​​​​​ 内 容&#xff1a;本任务使用DHT11温度传感器实现基本的端口配置、初始化时序。 学 时&#xff1a;3…

深度学习——样式迁移(笔记)

样式迁移&#xff1a;计算机视觉应用之一 1.将样式图片中的样式&#xff08;比如油画风格&#xff09;迁移到内容图片上&#xff0c;得到合成的图片 2.基于CNN的样式迁移 ①初始化合成图像&#xff0c;将其初始化内容图像。合成图像是风格迁移过程中唯一需要更新的变量X&#…

ChatGPT一战封神,和它聊完后,我更想躺平了

11 月 30 日&#xff0c;OpenAI 发布了名为 ChatGPT 的 AI 对话模型&#xff0c;它是 InstructGPT 的衍生模型&#xff0c;是根据 GPT-3.5 系列中的一个模型微调而来的&#xff0c;于 2022 年初完成训练。 用户规模达到 100 万需要多久&#xff1f;Netflix 用了三年半时间&…

刷题记录:牛客NC26257小雨坐地铁 [分层图跑最短路]

传送门:牛客 题目描述: 题目暂略 输入: 5 2 1 4 2 2 3 1 3 5 2 1 4 2 3 4 5 输出: 7一道分层图的经典题型,可以细细体会,这道题模拟出了经典的分层图题型 主要思路: 首先我们那道这道题应该不难想到最短路(这是显然的吧).大多数人应该都是卡在了建边的部分.每一条线路各自的…

【不共视的天敌】手眼标定AX = XB

一 . 手眼标定的作用及目的 机器人视觉应用中,手眼标定是一个非常基础且关键的问题。简单来说手眼标定的目的就是获取机器人坐标系和相机坐标系的关系,最后将视觉识别的结果转移到机器人坐标系下。 手眼标定行业内分为两种形式,根据相机固定的地方不同,如果相机和机器人末…

49. 残差网络(ResNet)

1. 加更多的层总是改进精度吗&#xff1f; 对于非嵌套函数类&#xff0c;较复杂&#xff08;由较大区域表示&#xff09;的函数类不能保证更接近“真”函数&#xff08; f* &#xff09;。这种现象在嵌套函数类中不会发生。 因此&#xff0c;只有当较复杂的函数类包含较小的函…

八、可变参数、stream流、异常

可变参数 介绍 定义方法参数的一种方式&#xff0c;方法的参数类型已经确定,个数不确定,我们可以使用可变参数 格式 修饰符 返回值类型 方法名(数据类型… 变量名) { }注意事项 可变参数的变量其实是一个数组如果一个方法有多个参数&#xff0c;包含可变参数&#xff0c;…

【ESP-Matter】matter协议学习笔记--以乐鑫方案为例

matter协议学习笔记--以乐鑫方案为例0. 写在前边的话1. matter 协议基本概念2. 设备间的本地自动化交互2.1 同步控制的实例&#xff1a;2.2 异步通知&#xff08;订阅、报告&#xff09;3. 桥接设备4. thread 边界路由器5. 专业名词0. 写在前边的话 以下学习笔记均参考乐鑫官方…

一位嵌入式初学者的2022年度总结

目录 学习过程 人工智能 嵌入式 51单片机 STM32 MicroPython Arduino 其他 未来计划 RT-Thread LIUNX 其他 总结 学习过程 今年二月份才开始写博客&#xff0c;到现在一共写了131篇文章&#xff0c;其中包含了Python&#xff0c;Mysql&#xff0c;51单片机&#…

阶段性回顾(1)

TIPS 1. 函数实参与形参地址不一样&#xff0c;形参的话有自己的内存空间与地址&#xff0c;当函数进行传值调用的时候&#xff0c;形参是实参的一份临时拷贝&#xff0c;各种对于形参的改变&#xff0c;对于实参来说不会产生任何影响。 2. 函数的实参与形参的关系就相当于两者…

Jacoco统计项目单元测试覆盖率

1.JaCoCo介绍&#xff1a; JaCoCo&#xff0c;即 Java Code Coverage&#xff0c;是EclEmma团队基于多年覆盖率库使用经验总结而研发的一个开源的Java代码覆盖率库。 代码覆盖&#xff08;英语&#xff1a;Code coverage&#xff09;是软件测试中的一种度量&#xff0c;描述程…

通信原理与MATLAB(九):DPSK的调制解调

目录1.差分编解码原理1.1差分编码原理1.2差分解码原理2.DPSK的调制原理3.DPSK的解调原理4.DPSK的代码5.结果图5.特点1.差分编解码原理 1.1差分编码原理 绝对码变相对码 如下图&#xff0c;绝对码10110&#xff0c;差分编码首先确定一个参考码元0&#xff0c;然后相对码bnan异…

像素旋转:一种在加密图像中实现安全的可逆数据隐藏方案

文章目录前言一、提出的PR-RDHEI方案二、算法步骤简介1.图像加密2.数据嵌入&#xff08;重点&#xff09;3.图像恢复&#xff08;重点&#xff09;总结收获与思考前言 原文题目《Reversal of pixel rotation: A reversible data hiding system towards cybersecurity in encry…

任意组件通信:全局事件总线、消息订阅与发布

全局事件总线&#xff1a;任意组件间通信 1、一个重要的内置关系&#xff1a; VueComponent.prototype.__proto__ Vue.prototype 2、为什么要有这个关系&#xff1a; 让组件实例对象&#xff08;vc&#xff09;可以访问到Vue原型上的属性&#xff0c;方法。 第一步&#xf…

mysql-JDBCDruid基本使用方法

JDBC 今日目标 掌握JDBC的的CRUD理解JDBC中各个对象的作用掌握Druid的使用 1&#xff0c;JDBC概述 在开发中我们使用的是java语言&#xff0c;那么势必要通过java语言操作数据库中的数据。 1.1 JDBC概念 JDBC 就是使用Java语言操作关系型数据库的一套API 全称&#xff1a;( …

网络编程 重叠IO模型

目录 1.概念 2.代码详解 事件通知实现逻辑​ 1.WSASocket函数 2.AcceptEx函数 3.WSARecv函数 4.WSAGetOverlappedTesult函数 5.WSAResetEvent函数 6.WSASend函数 ##重叠IO模型事件通知整体代码 完成例程实现逻辑​编辑 ##重叠IO模型完成例程的整体代码 1.概念 重叠IO模型是对…

微信小程序集成three.js--1.创建各种光源的场景

1.实例演示 微信小程序集成Three.js&#xff0c;各种光源效果演示2.源码 &#xff08;1&#xff09;引入three.js库文件 import * as THREE from ../../libs/three.weapp.js import {OrbitControls } from ../../jsm/controls/OrbitControls const app getApp() 库文件下载…

第七章面向对象编程

第七章面向对象编程 7.1对象在内存中存在形式 7.1.1属性/成员变量/字段&#xff08;field) 1.属性成员变量字段field&#xff0c;概念上相等 public class Object02 {//编写一个 main 方法public static void main(String[] args) {}} class Car {String name;//属性, 成员变…

数据防泄露之图文档及业务数据经验分享

场景描述 信息化时代发展迅速&#xff0c;数据防泄露一词也频繁的出现在我们身边。无论企业或政府单位&#xff0c;无纸化办公场景越来越多&#xff0c;数据泄露的时间也层出不穷。例如&#xff1a;世界最大职业中介网站Monster遭到黑客大规模攻击&#xff0c;黑客窃取在网站注…