C++数据结构--跳表的思想--手把手教你实现跳表--0721

news2024/9/29 19:16:13

1、 跳表--skiplist

skiplist本质上是一种查找结构,跟平衡搜索树和哈希表的价值是一样的。跳表首先是一个链表,它是在链表的基础上发展的。但一般的链表进行查找数据只能全部遍历,时间复杂度为O(n)。

William Pugh的优化:

  • 假如每相邻两个节点升高一层,增加一个指针,让该指针指向下下个节点。

所有新增加的指针连成了一个新的链表,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。

  • 在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一个指针,从而产生第三层链表。查找效率可以进一步提升

  •  按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似二分查找,使得查找的时间复杂度可以降低到O(log n)

但问题在于在插入或者删除时,如果严格遵守上述规则就需要把后续被影响的节点的指向全部修改,就又需要重新遍历一遍。时间复杂度又上升为O(n)。

  • William Pugh做了一个大胆的处理,不再严格要求对应比例关系,而是插入一个节点的时候随机出一个层数。这样每次插入和删除都不需要考虑其他节点的层数。

 2、 随机的层数

一般跳表会设计一个最大层数maxLevel的限制,其次会设置一个多增加一层的概率p。那么计算这个随机层数的伪代码如下图:

节点层数恰好等于1的概率为1-p。
节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。
节点层数大于等于3的概率为p^2,而节点层数恰好等于3的概率为p^2*(1-p)。
节点层数大于等于4的概率为p^3,而节点层数恰好等于4的概率为p^3*(1-p)。                                  ...
 一个节点的平均层数计算结果为 1/(1-p)

跳表的平均时间复杂度为O(logN),推导过程可查询其他大佬。


3、跳表的模拟实现

准备工作

跳表节点的设想:首先跳表有一个层数,每一层都有存有一个指针指向下一个位置。我们以vector作为容器进行存储。在初始化列表阶段直接使用vector的构造函数。

struct SkiplistNode
{
    vector<SkiplistNode*> _nextV;
    int _val;
    SkiplistNode(int val,int level)
        :_val(val)
        ,_nextV(level,nullptr)
    {}
};

综上,我们创建一个节点都需要一个随机数来充当层数,在设计跳表时,要注意设置最大层数_maxlevel和概率_p

class Skiplist
{
    typedef SkiplistNode Node;
public:
    Skiplist()
    {
        _head=new Node(-1,1);//头结点的值设为-1 层数为1
                            //也可以不是1 直接设为最大层数
    }
    //开始画饼
    int Randomlevel()
    {}
    bool search(int target)
    {}
    void add(int num)
    {}
    bool erase(int num)
    {}
private:
    double _p=0.25;
    size_t _maxlevel=32;//2^32次方是 unsigned int能存下的最大的数
    Node* _head;//跳表需要一个头结点
};

3.2 随机函数

C++11新增有库可以实现随机值,但比较难记。

	int Randomlevel()
	{
		static std::default_random_engine generator(std::chrono::system_clock::now().time_since_epoch().count());
		static std::uniform_real_distribution<double> distribution(0.0, 1.0);

		size_t level = 1;
		while (distribution(generator) <= _p && level < _maxLevel)
		{
			++level;
		}

		return level;
	}

 C语言相比就比较简单,C语言的rand()函数会有一个最大值,是用宏定义的 RAND_MAX

int Randomlevel()
{
    size_t level=1;
    while(rand()<= RAND_MAX*_p && level<_maxlevel)
    {
        level++;
    }
    return level;
}

3.3 查

bool search(int target)
{
    Node*cur=_head;
    int level=_head->_nextV.size()-1; 
    //我们从最高层的下一个指向开始找 这样找的快
    while(level>=0) //是有第0层的
    {
        //我比你大 那就直接横着跨走 
        //注意当下一层是nullptr时 在访问_val就报错了
        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;
}

3.4 增

如果我们要新增一个节点,首先需要的就是知道插入节点的前后节点,以便将这些节点相互链接起来。


 此时prevV中就存放了 所有前一个指针。

当我们随机好了新节点的层数时,可以从最底层开始逐个链接,直至到达了新节点的层数。(如果新节点的层数超过了根节点的层数,根节点的层数需要更新)

要实现add 需要先实现确定prevV的函数

vector<Node*> FindPrevNode(int num)

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)
        {
            //先更新prevV
            prevV[level]=cur;
            --level;
        }
    }
    return prevV;
}

void add(int num)

void add(int num)
{
    vector<Node*> prevV=FindPrevNode(num);
    int n=Randomlevel();
    Node* newnode=new Node(num,n);
    if(n>_head->_nextV.size())
    {
        //头结点层数变高 新增层数直接指向nullptr
        _head->_nextV.resize(n,nullptr);
        //prevV更新 新增层数的前一个指向_head 
        prevV.resize(n,_head);
    }
    //链接前后节点
    for(int i=0;i<n;i++)
    {
        newnode->_nextV[i]=prevV[i]->_nextV[i];
        prevV[i]->_nextV[i]=newnode;
    }

}

3.5 删

删除同样是需要拿到prevV数组 并修改指针的指向 最后Delete掉当前节点

bool erase(int num)
{
    vector<Node*> prevV=FindPrevNode(num);
    //查看一下在不在该跳表
    //一定要注意判断是否为空 访问空指针是会出问题的
    if(prevV[0]->_nextV[0]==nullptr || prevV[0]->_nextV[0]->_val !=num)
    {
        return false;
    }
    //保存要删除的节点
    Node* cur=prevV[0]->_nextV[0];
    for(int i=0;i<cur->_nextV.size();i++)
    {
        prevV[i]->_nextV[i]=cur->_nextV[i];
    }
    delete cur;
    return true;
}

3.6 测试代码及结果

由于跳表的打印要想打出图片的结果比较复杂 这里不再给出打印函数。可通过leetcode 题目编号1206.设计跳表进行判断。

力扣

 

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

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

相关文章

Kafka一次线上问题

线上问题&#xff1a; Kafka: 客户说Broker不时会发生些错误日志&#xff0c;也看到topic的tps下降很快很明显&#xff0c; 日志看是ISR在不断的伸缩&#xff0c; 监控发现发生问题时的CPU、IO、磁盘都没有瓶颈 再查看堆栈信息&#xff1a;可看到关键信息&#xff1a; 有锁…

R语言raster包批量读取单一或大量栅格图像

本文介绍基于R语言中的raster包&#xff0c;读取单张或批量读取多张栅格图像&#xff0c;并对栅格图像数据加以基本处理的方法。 1 包的安装与导入 首先&#xff0c;我们需要配置好对应的R语言包&#xff1b;前面也提到&#xff0c;我们这里选择基于raster包来实现栅格图像数据…

TestStand-单执行界面

文章目录用户界面运行流程例程创建前面板设置用户界面主窗口前面板控件配置用户事件用户界面运行流程 用户界面设计的基本元素&#xff1a;管理控件、可视化控件、连接、应用程序启动及关闭、注册事件、处理事件。 LabVIEW中通过Regesiter Event Callback注册事件。 LabVIEW-Te…

flink内存管理, 增加Task内存大小,减少ManageMemory, network内存的方法

问题描述 flink默认分配的内存&#xff0c;不合理&#xff0c;jvm 堆内存太小&#xff0c;其他内存太大。向yarn申请8G内存&#xff0c;最后分配到heap的大小才3.2G&#xff0c;不是让人抓狂吗&#xff1f; 以上是&#xff0c;向yarn申请8G内存&#xff0c;实时分配的内存是上…

“破壁者”氚云,打破低代码之困

互联网云大厂的“火”已经烧红了低代码领域的半边天。 自低代码在国内盛行以来&#xff0c;尤其是时至2022年末&#xff0c;阿里、腾讯、华为等云大厂的跑马圈地仍如火如荼&#xff0c;动作密集程度堪比机关枪。 面对日益增长的企业数字化业务需求&#xff0c;产品经理只需少…

编译器设计(十三)——指令调度

一、简介 对程序块或过程中的操作进行排序以有效利用处理器资源的任务称为指令调度&#xff08;instruction scheduling&#xff09;。调度器的输入是由目标机汇编语言操作组成的一个部分有序的列表&#xff0c;输出是同一列表的一个有序版本。 一组指令的执行时间严重依赖于…

什么是云存储?有什么优势?

在云计算中&#xff0c;用户将数据保存在远程位置。它可以通过互联网连接访问&#xff0c;而不是在本地或物理上(在硬盘上)访问。而云存储成为最实用有效的方式之一。它有助于在线存储数据。 什么是云存储? 云存储是指安全、全局和可扩展的数据存储。它用于存储不可变数据&…

智慧工厂的大脑——APS生产排程系统

生产计划排程是生产管理中的核心工作&#xff0c;或许很多人不同意这个观点&#xff0c;只是因为这个观点的前提是生产计划排程在生产管理中真正起到了作用&#xff0c;目前国内制造业的现状还不能体现出生产计划排程的真正作用&#xff0c;所以也没有人认为它是最核心的工作&a…

蓝桥杯Python练习题11-闰年判断

资源限制   内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述   给定一个年份&#xff0c;判断这一年是不是闰年。 当以下情况之一满足时&#xff0c;这一年是闰年&#xff1a;   1. 年…

“集合划分问题”如何解决?这里教你个妙招,轻松掌握这类问题~

目录 “集合划分”这类问题的解题思路 一、划分为k个相等的子集 二、火柴拼正方形 “集合划分”这类问题的解题思路 这类题一般都会描述成这个样子&#xff1a;“给你一个数组&#xff0c;是否能将他划分成n个数值相等的子集&#xff1f;”&#xff0c;再或者有些可能题目描述…

MinIO高性能对象存储

一、MinIO高性能对象存储 MinIO是一个高性能对象存储解决方案&#xff0c;它提供了与Amazon Web Services S3兼容的API&#xff0c;并支持所有核心S3功能。 MinIO旨在部署任何地方—公共或私有云、裸机基础设施、协调环境和边缘基础设施。本文档说的是Windows平台上MinIO部署的…

接口测试自动化框架选型

1、fiddler fiddler 是一个 HTTP 协议调试代理工具&#xff0c;Web 和手机测试都会用到&#xff0c;同时也支持接口测试。它能够记录并检查所有你的电脑和互联网之间的 http 通讯&#xff0c;设置断点&#xff0c;查看所有的“进出”Fiddler 的数据(指 cookie,html,js,css 等文…

ASP.NET大型药品销售ERP系统源码

ASP.NET医药ERP管理系统源码 药品销售管理系统源码 源码分享&#xff01;需要源码学习可私信我。 一、源码特点 1、渠道销售商在把药品从厂商销售到医院时&#xff0c;需要管理大量的数据&#xff0c;这些通常包括药品从厂商采购数据、药品销售到商业公司的数据&#xff0c;以…

Python文件基础操作(6)

python学习之旅(六) &#x1f44d;基础语法部分笔记(一) &#x1f44d;条件判断部分笔记(二) &#x1f44d;循环语句部分笔记(三) &#x1f44d;函数使用部分笔记(四) &#x1f44d;数据容器部分笔记(五) &#x1f44d;文件操作部分笔记(六) 一.文件编码 编码就是一种规则集合&…

数据湖---hudi核心概念

文章目录TimelineTable & Query TypesTable Types查询类型COWMOR索引Hudi索引类型索引选择策略File Layouts元数据表元数据表的动机研究中的一些数字&#xff1a;支持多模态索引写操作操作类型UPSERTINSERTBULK_INSERTDELETE写入路径schema 演进key生成并发控制Datasource …

强强联合,怿星科技艾拉比携手斩获“铃轩奖”

12月23日—24日&#xff0c;汽车行业的年度盛典2022中国汽车供应链峰会&#xff08;CASCS2022&#xff09;盛大开幕&#xff0c;全国汽车供应链大咖再次聚首中国车谷&#xff0c;怿星科技CEO潘凯在圆桌《新汽车软件到底怎么办》上分享了观点。会议同期&#xff0c;国内最具权威…

十年底层创新,2023年亚马逊云科技或再创新高

2006年&#xff0c;亚马逊云科技推出了第一代公有云产品Amazon S3和Amazon EC2&#xff0c;由此开创了企业IT的历史——云计算从此开始改变整个企业IT市场。2013年&#xff0c;亚马逊云科技再次开创了历史&#xff0c;推出了首个自研芯片Amazon Nitro&#xff0c;由此打开了全球…

屏幕录制下载安装?这3个软件,亲测好用

很多小伙伴在使用电脑进行学习、娱乐和工作的时候&#xff0c;或多或少遇到过需要使用屏幕录制功能的时候。那么有什么特别好用的屏幕录制软件吗&#xff1f;屏幕录制下载安装怎么进行&#xff1f;今天小编分享3款软件&#xff0c;特别好用。 屏幕录制下载1&#xff1a;爱拍录屏…

朗润国际期货技术分析——日内交易图表类型

一张图表胜过千言万语。你以前听说过这句话&#xff0c;对吗&#xff1f;在日内交易员的世界里&#xff0c;一张图表往往比千言万语更有价值。它不仅告诉我们过去发生了什么&#xff0c;而且告诉我们一个市场在未来可能会做什么。它将继续走高还是走低&#xff1f;它是否处于一…

第十一篇 1+X考证 Web前端测试题MySQL篇(新)

单选题 1、下列关于MySQL备份的说法中&#xff0c;错误的是&#xff08; B &#xff09; A、备份数据库的命令是mysqldump B、备份数据库的文件扩展名必须是.sql C、“mysql”命令可以还原数据库 D、可以同时备份一个或多个数据库 [ 解析&#xff1a;.txt ] 2、在MySQL…