【C++高阶数据结构】跳表(skiplist)

news2024/12/23 10:08:42

🏆个人主页:企鹅不叫的博客

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙

【C++高阶数据结构】并查集

【C++高阶数据结构】图

【C++高阶数据结构】LRU

【C++高阶数据结构】B树、B+树、B*树


文章目录

  • 💙系列文章💙
  • 💎一、概念
  • 💎二、实现
  • 💎三、性能分析


💎一、概念

  1. 假如我们每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点,如下图b所示。这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半。由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。

  2. 以此类推,我们可以在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一个指针,从而产生第三层链表。如下图c,这样搜索效率就进一步提高了。

  3. 实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似二分查找,使得查找的时间复杂度可以降低到O(log n)。在这里插入图片描述

  4. 不再严格要求对应比例关系,而是插入一个节点的时候随机出一个层数

在这里插入图片描述

一个结点到底应该给几层?

一个节点的平均层数(也即包含的平均指针数目),计算如下:

在这里插入图片描述

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

💎二、实现

设计跳表

在这里插入图片描述

img

在这里插入图片描述

思路:

  1. 首先设计节点,包含节点值和存储next指针
  2. 跳表设计,定义一个头节点,最大层数,生成层数概率
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]之间
        //相当于[0, 1]的范围
        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/147437.html

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

相关文章

第十一章Thymeleaf学习

文章目录什么是Thymeleaf什么是模板引擎Thymeleaf的同行Thymeleaf优势一个实例来认识大概过程导入对应的jar包配置对应的xml文件对应的ViewBaseServlet编写——对应的模板引擎写对应的Servlet类并且继承ViewBaseServlet对应index.html资源——对应的模板Thymeleaf的基础语法th名…

337. 打家劫舍 III

目录题目思路代码题目 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”…

笔试强训(11)

第一题:二进制插入二进制插入__牛客网 给定32位整数n和m&#xff0c;同时我们指定i和j&#xff0c;将m的二进制位数插入到n的二进制位数的j到i位&#xff0c;我们保证n的j到i位均是等于0的&#xff0c;况且m的二进制位数小于等于i-j1&#xff0c;其中二进制的位数从0开始从低到…

js设计模式(八)-总体感受一下设计模式

前言 首先&#xff0c;不得不说我们是站在巨人的肩膀上写代码&#xff0c;前辈们已经很合理的帮助我们总结出来了23种设计模式&#xff0c;虽然有些已经被语言直接使用Api实现了&#xff0c;感谢走在前沿的攻城狮。 但是真真正正的看一遍所有的设计模式还是很有必要的&#x…

MyBatis查询数据库

1.MyBatis 是什么&#xff1f; MyBatis 是⼀款优秀的持久层框架&#xff0c;它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO&#xf…

计算机基础——计算机分类

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会讲解计算机分类应用领域以及发展趋势 一.计算机分类 计算机并非只有日常所…

并行计算 Clion配置使用OpenMP

文章目录配置CMakeList.txt文件OpenMP之HelloWorld数据共享属性shared子句private子句default子句default(shared)default(none)配置CMakeList.txt文件 文件底部加入以下内容&#xff0c;即可支持OpenMP FIND_PACKAGE(OpenMP REQUIRED) if (OPENMP_FOUND)message("OPENM…

STM32MP157驱动开发——Linux DAC驱动

STM32MP157驱动开发——Linux DAC驱动0.前言一、DAC 简介二、驱动源码分析1.设备树下的 DAC 节点2.驱动源码分析1&#xff09;stm32_dac 结构体2&#xff09;stm32_adc_probe 函数3&#xff09;stm32_dac_iio_info 结构体三、驱动开发1.修改设备树2.使能DAC驱动四、 运行测试0.…

读书笔记 -公司改造 和 紧迫感

读书笔记 -公司改造 - 三枝匡 读书笔记 -公司改造 - 三枝匡 2022 年夏天的时候在微信读书上读了这本书&#xff0c;这是我们 CSDN 的创始人蒋涛推荐的&#xff0c;当时记了一些笔记如下。 总结&#xff1a; 每个有一定的历史&#xff0c;比较成功、或者尚未非常成功的公司遇…

基于Java+SpringBoot+vue+element实现毕业就业招聘系统

基于JavaSpringBootvueelement实现毕业就业招聘系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…

用最简单的案例带你掌握C++中各种指针

1、前言 指针&#xff0c;作为C/C中最神秘、功能最强大的语法&#xff0c;着实是难以理解 、难以掌握、难以运用。&#x1f625; 但是&#xff0c;能灵活的使用指针&#xff0c;对利用C/C开发程序将有很大的帮助&#xff0c;让我们一起来了解了解吧。 2、啥是指针&#xff1f…

参加《2022 中国开发者影响力盛典》我的 4 重收获!

感谢 CSDN 邀请&#xff0c;西红柿有幸参加了 2022 中国开发者影响力盛典暨 CSDN 企业生态汇&#xff0c;让我有了一个不虚此行的下午&#xff0c;也跟大家分享一下我在会上的 4 重收获吧~第一重收获&#xff1a;互联网圈大佬 会议聚焦开发者生态建设主题&#xff0c;分享了 CS…

分布式基础篇4 —— 基础篇完结

分类维护一、三级分类后端实现准备工作跨域问题关闭 ESLint 检查前端实现二、分类删除前端完善分类列表后端实现——删除配置发送请求代码片段前端实现——删除三、分类增加前端实现四、分类修改五、拖拽菜单拖拽效果实现拖拽数据收集拖拽功能完成拖拽功能完善六、批量删除品牌…

JS知识补充-JS原型链

概述JS原型链别名&#xff1a;隐式原型链作用&#xff1a;根据一定路径查找属性&#xff08;方法&#xff09;作用举例&#xff1a;我们定义一个构造函数Fn&#xff0c;使用此构造函数创建一个对象fn1&#xff0c;接着使用创建的对象fn1去调用toString方法并打印&#xff0c;我…

【阶段三】Python机器学习03篇:机器学习中的函数、机器学习中的梯度下降、机器学习的数据结构:张量与机器学习概率与统计基础

本篇的思维导图: 机器学习中的函数 函数描述了输入与输出的关系。在函数中,一个事物(输出)随着另一个(或一组)事物(输入)的变化而变化,如下图所示。 输入与输出的关系一般情况下,用x(或x1,x2,x3,…)表示输入,用y表示输出,并把它们叫作变量,…

Java设计模式中的设计原则/开闭原则、里氏代换原则和依赖倒转原则又是什么,怎么用

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 3.设计原则 3.1 目的 提高软件系统可维护性与可复用性增加软件可扩展性与灵活性节约开发成本与维护成本 3.2 开闭原则 3.2.1 特点 对扩展开放&#xff0c;对修…

实战干货|自研数据存储迁移MySQL实战

背景 最近公司内部在做某自研数据存储的下线工作&#xff0c;这里我们暂且化名其为DistributeSQL&#xff0c;由于DistributeSQL不再进行服务支持&#xff0c;需要迁移项目中使用到该存储到其他数据存储中。 本篇来聊聊这次在数据存储迁移过程中的方案设计思路、实现的大致细节…

中老年服装电商小程序开发

近年来&#xff0c;随着网络的发展&#xff0c;中老年服装电商小程序开发有了很大的进步。这种平台不仅可以方便用户购买到最新最时尚的产品&#xff0c;而且还能帮助商家提高销售业绩。 1&#xff1a;中老年服装电商小程序开发的优势 中老年人对商品信息需求大、容易接受新鲜…

实验⼀:Windows主机漏洞利⽤攻击实践

永恒之蓝简介 永恒之蓝&#xff08;Eternal Blue&#xff09;爆发于2017年4月14日晚&#xff0c;是一种利用Windows系统的SMB协议漏洞来获取系统的最高权限&#xff0c;以此来控制被入侵的计算机。甚至于2017年5月12日&#xff0c; 不法分子通过改造“永恒之蓝”制作了wannacry…

【ROS】—— ROS重名问题(九)

文章目录前言1. ROS工作空间覆盖2. ROS节点名称重名2.1 rosrun设置命名空间与重映射2.1.1 rosrun设置命名空间2.1.2 rosrun名称重映射2.1.3 rosrun命名空间与名称重映射叠加2.2 launch文件设置命名空间与重映射2.3 编码设置命名空间与重映射2.3.1 重映射2.3.2 C 实现:命名空间3…