1206. 设计跳表

news2024/10/5 19:18:09

1206. 设计跳表

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

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

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

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

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

bool search(int target) : 返回target是否存在于跳表中。
void add(int num): 插入一个元素到跳表。
bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。
注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。

跳表结点 SkipNode

struct SkipNode {
    int val;
    vector<SkipNode*> next;
    SkipNode(int val, int level) :val(val), next(level, nullptr) {};
};

跳表 Skiplist 

class Skiplist {
private:
    SkipNode* head;//头结点(初始化为最高层数) 
    int level;//当前跳表的最高层数(除了头结点) 
public:
    bool search(int target)
    {
        从当前最高层开始遍历,如果当前结点的下一个结点大于target(或者为nullptr),
        则进入当前结点的下一层,继续向右遍历。
    }
    void add(int num)
    {
        创建一个新结点,给它随机一个层数newlevel,然后更新当前最高层数;
        接着从当前最高层开始遍历,如果当前结点的下一个结点大于target(或者为nullptr),
        则让新结点指向当前结点的下一个结点,当前结点指向新结点
        注意:自顶向下,从newlevel ~ 0都要连一次
    }
    bool erase(int num)
    {
        从当前最高层开始遍历,如果当前结点的下一个结点大于target(或者为nullptr),
        则进入当前结点的下一层,继续向右遍历。
        如果当前结点的下一个结点等于target,则让当前结点指向下一个的下一个结点,完成删除
        注意:自顶向下,要删的结点,每一层都要删一次

        删除完成之后,记得更新当前跳表的最高层数       
    }
};

实现代码: 

#include <bits/stdc++.h>
using namespace std;
const int MAX_LEVEL = 8;//最高8层 

struct SkipNode {
    int val;
    vector<SkipNode*> next;
    SkipNode(int val, int level) :val(val), next(level, nullptr) {};
};

class Skiplist {
private:
    SkipNode* head;//头结点(初始化为最高层数) 
    int level;//当前跳表的最高层数(除了头结点) 
public:
    Skiplist() :head(new SkipNode(-1, MAX_LEVEL)), level(0) {}
    int randomLevel()//插入一个新结点时,随机它的层数 
    {
        int rlevel = 1;
        while (rand() % 2 == 0 && rlevel < MAX_LEVEL)//如果随机出 0,层数+1,如果随机出 1,结束递增 
        {
            rlevel++;
        }
        return rlevel;
    }
    //查找 
    bool search(int target)
    {
        SkipNode* cur = head;
        for (int i = level - 1; i >= 0; i--)//从最高层开始 
        {
            while (cur->next[i] != nullptr && cur->next[i]->val < target)//向右遍历,直至当前结点的下一个结点 >= target
            {
                cur = cur->next[i];
            }
            if (cur->next[i] != nullptr && cur->next[i]->val == target)
            {
                return true;
            }
            //如果当前结点的下一个结点 > target,则进入下一层 
        }
        return false;
    }
    //添加 
    void add(int num)
    {
        int newlevel = randomLevel();//随机一个层数 
        if (newlevel > level)//更新 level 
        {
            level = newlevel;
        }
        SkipNode* newNode = new SkipNode(num, newlevel);
        SkipNode* cur = head;
        for (int i = level - 1; i >= 0; i--)
        {
            while (cur->next[i] != nullptr && cur->next[i]->val < num)
            {
                cur = cur->next[i];
            }
            if (i < newlevel)//插入一个新增结点 
            {
                newNode->next[i] = cur->next[i];
                cur->next[i] = newNode;
            }
        }
    }
    bool erase(int num)
    {
        SkipNode* cur = head;
        SkipNode* toDelete = nullptr;
        for (int i = level - 1; i >= 0; i--)
        {
            while (cur->next[i] != nullptr && cur->next[i]->val < num)
            {
                cur = cur->next[i];
            }
            if (cur->next[i] != nullptr && cur->next[i]->val == num)
            {
                toDelete = cur->next[i];
                cur->next[i] = toDelete->next[i];//在 当前这一层 上删除目标结点 
            }
        }
        //更新 level 
        while (level >= 1 && head->next[level - 1] == nullptr) {
            level--;
        }
        //delete 防止内存泄露 
        if (toDelete != nullptr) {
            delete toDelete;
            return true;
        }
        else {
            return false;
        }
    }
};

int main()
{
    Skiplist* skiplist = new Skiplist();
    skiplist->add(1);
    skiplist->add(2);
    skiplist->add(3);
    cout << skiplist->search(0) << endl;   // 返回 0
    skiplist->add(4);
    cout << skiplist->search(1) << endl;   // 返回 1
    cout << skiplist->erase(0) << endl;    // 返回 0
    cout << skiplist->erase(1) << endl;    // 返回 1
    cout << skiplist->search(1) << endl;   // 返回 0
}

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

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

相关文章

如何收集EMC VPLEX 日志和VPLEX日志的简单解读

对于VPLEX遇到的问题&#xff0c;和二线沟通最快最有效的办法就是收集完整的日志&#xff0c;而不是拍一个照片。本文就详细介绍如何收集日志&#xff1f;和那些日志文件对我们分析问题是有价值的。 命令行ssh登录Vplex 管理控制台&#xff0c;然后进入Vplexcli命令行&#xf…

Windows安装Nginx并配置负载均衡

Nginx简介 Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器&#xff1b;同时也是一个IMAP、POP3、SMTP代理服务器&#xff1b;Nginx可以作为一个HTTP服务器进行网站的发布处理&#xff0c;另外Nginx可以作为反向代理进行负载均衡的实现。 Nginx使用基于事件驱动…

HOJ项目部署(前后端及其判题机)

文章目录HOJ项目部署1 项目准备1.1 拉取HOJ项目到本地1.2 项目包结构解读2 后端部署DataBackup2.1 环境准备1&#xff09;项目基本数据导入2&#xff09;Nacos环境配置2.2 修改配置1&#xff09;修改application-prod.yml2&#xff09;修改bootstrap.yml3&#xff09;修改上传文…

【模板特性补充】

目录&#xff1a;前言一、非类型模板参数使用方法使用场景二、模板特化&#xff08;一&#xff09;函数模板特化&#xff08;二&#xff09;类模板特化1.全特化2.偏特化使用场景三、模板分离编译1. 什么是分离编译2.模板的分离编译3.解决方法四、模板总结前言 打怪升级&#x…

你知道多少接口性能优化的方案?

一、背景 针对老项目&#xff0c;去年做了许多降本增效的事情&#xff0c; 二、接口优化方案总结 1.批处理 批量思想&#xff1a;批量操作数据库&#xff0c;这个很好理解&#xff0c;我们在循环插入场景的接口中&#xff0c;可以在批处理执行完成后一次性插入或更新数据库…

Springboot的自动注入

一、开篇 在平时的开发过程中用的最多的莫属springboot了&#xff0c;都知道springboot中有自动注入的功能&#xff0c;在面试过程中也会问到自动注入&#xff0c;你知道自动注入是怎么回事吗&#xff0c;springboot是如何做到自动注入的&#xff0c;自动注入背后的原理是什么&…

基于 RocketMQ Connect 构建数据流转处理平台

作者&#xff1a;周波&#xff0c;阿里云智能高级开发工程师&#xff0c; Apache RocketMQ Committer 01 从问题中来的 RocketMQ Connect 在电商系统、金融系统及物流系统&#xff0c;我们经常可以看到 RocketMQ 的身影。原因不难理解&#xff0c;随着数字化转型范围的扩大及…

Linux学习笔记——多线程

文章目录补充知识Linux线程概念线程的优点线程的缺点线程异常线程用途多进程的应用场景Linux进程VS线程重新理解进程进程和线程线程共享的进程资源和环境为什么线程切换的成本更低进程和线程的关系Linux线程控制POSIX线程库创建线程线程ID及进程地址空间布局线程终止线程等待线…

pycharm常用的插件

pycharm等IDE 无法编辑bat文件 Chinese Language Pack 汉化插件 vim CodeGlance 右侧地图 将类似于Sublime中的代码小地图嵌入到编辑器窗格中。使用自定义颜色进行语法高亮&#xff0c;同时使用明暗主题。 Rainbow Brackets&#xff08;必备推荐&#xff09;收费了 这个插件…

【数据库原理 • 六】数据库备份与恢复

前言 数据库技术是计算机科学技术中发展最快&#xff0c;应用最广的技术之一&#xff0c;它是专门研究如何科学的组织和存储数据&#xff0c;如何高效地获取和处理数据的技术。它已成为各行各业存储数据、管理信息、共享资源和决策支持的最先进&#xff0c;最常用的技术。 当前…

Baumer工业相机堡盟工业相机中预处理相机的特性优势以及行业应用

Baumer工业相机堡盟工业相机如何通过BGAPISDK里显示彩色相机和黑白相机的图像&#xff08;C#&#xff09;Baumer工业相机Baumer工业相机的预处理相机的技术背景Baumer工业相机中预处理相机的特性Baumer工业相机中图像压缩相机的特性Baumer工业相机中3D激光相机的特性Baumer工业…

ESP32设备驱动-MAX30102脉搏血氧饱和度和心率监测传感器驱动

MAX30102脉搏血氧饱和度和心率监测传感器驱动 文章目录 MAX30102脉搏血氧饱和度和心率监测传感器驱动1、MAX30102介绍2、硬件准备3、软件准备4、驱动实现1、MAX30102介绍 MAX30102是一款集成脉搏血氧饱和度和心率监测生物传感器模块。 它包括内部 LED、光电探测器、光学元件和…

网页滚动体验,IScroll滚动插件,你安装了类似的滚动页面插件吗

IScroll是一款基于JavaScript的插件&#xff0c;用于在网页中实现平滑滚动效果。 这个插件可以帮助用户创建回到页面顶部和底部的按钮、生成页面导航快照&#xff0c;以及设置滚动时间等功能&#xff0c;从而提升网页的用户体验。 IScroll的特点在于&#xff0c;它能够平滑地…

【15】数据操作reshape、张量的运算

1. N维数组 ① 机器学习用的最多的是N维数组&#xff0c;N维数组是机器学习和神经网络的主要数据结构。 2. 创建数组 ① 创建数组需要&#xff1a;形状、数据类型、元素值。 3. 访问元素 ① 可以根据切片&#xff0c;或者间隔步长访问元素。 ② [::3,::2]是每隔3行、2列访问…

考研数据结构--线性表

线性表 文章目录线性表概述线性表的特点线性表的基本操作线性表的顺序表示概述优缺点操作顺序表的定义顺序表的初始化顺序表的插入顺序表的删除顺序表的查找顺序表的输出顺序表的判空顺序表的销毁main方法测试线性表的链式表示概述优缺点单链表操作单链表的定义单链表的初始化单…

自动化面试题3

1、PLC输入输出如何接线&#xff0c;源性漏型如何看&#xff1f; 所谓“源型输入”&#xff0c;是指电流从模块的公共端流入&#xff0c;从模块的输入通道流出的接线方式。源型输入的公共端作为电源正极&#xff08;共阳极&#xff09;。源型输入可以等效为在输入模块外部连接…

C# | 上位机开发新手指南(九)加密算法——RSA

上位机开发新手指南&#xff08;九&#xff09;加密算法——RSA 文章目录上位机开发新手指南&#xff08;九&#xff09;加密算法——RSA前言RSA的特性非对称性安全性可逆性签名速度较慢密钥管理RSA算法的参数公钥公钥指数e模数n私钥私钥指数d模数n质数p和qdp和dqqInv质数填充方…

性能测试,python 内存分析工具 -memray

Memray是一个由彭博社开发的、开源内存剖析器&#xff1b;开源一个多月&#xff0c;已经收获了超8.4k的star&#xff0c;是名副其实的明星项目。今天我们就给大家来推荐这款python内存分析神器。 Memray可以跟踪python代码、本机扩展模块和python解释器本身中内存分配&#xf…

yolov5-v7.0实例分割快速体验

简介 &#x1f680;yolov5-v7.0版本正式发布&#xff0c;本次更新的v7.0则是全面的大版本升级&#xff0c;最主要的功能就是全面集成支持了实例分割&#xff0c;yolov5已经集成检测、分类、分割任务。 前面几篇文章已经介绍过关于Yolov5的一些方面 yolov5目标检测:https://bl…

[计算机图形学]几何:曲线和曲面(前瞻预习/复习回顾)

一、曲线 1.Bzier Curves—贝塞尔曲线 贝塞尔曲线也是一种显式的几何表示方法。贝塞尔曲线定义了一系列的控制点&#xff0c;致使确定满足这些控制点关系的唯一一条曲线&#xff1a;如上图定义的贝塞尔曲线满足 起始点为p0&#xff0c;结束点为p3&#xff0c;起始点的切线方向…