数据结构:跳表实现(C++)

news2024/11/7 5:01:48

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》《网络》 《redis学习笔记》

文章目录

  • 前言
  • 跳表
    • 跳表的优化思路
    • skiplist,平衡搜索树,哈希表的对比
  • 实现思路
    • SkiplistNode
    • search 搜索
    • add 增加
    • earse 删除
  • 整体代码
  • 总结


前言

本文使用C++实现跳表的增删查。

铁蕾大佬关于跳表的博客


跳表

跳表(SkipList)是一种用于有序数据的高效数据结构,由 William Pugh 在1989年提出。
跳表本质是一个查找结构,通过在原有的有序链表上面增加多级索引来实现快速查找;跳表可以看作是一种可以进行二分查找的有序链表。

跳表的优化思路

  • 有序链表的问题:有序链表虽然保持了数据的顺序,但在查找特定元素时,需要从头节点开始顺序遍历,时间复杂度为O(N),其中N为链表中元素数量。这在数据量较大时,会导致查找效率较低

跳表的优化思路
假如我们每相邻两个节点增加一个指针,让这个指针指向下下个节点。
在这里插入图片描述

这样所有新增加的指针连成一个新链表,但它包含的节点个数只有原来的一半。此时,我们再查找特定元素时,需要比较的节点数量大概只有原来的一半。
跳表就是采用多层链表的思路。
在这里插入图片描述

在这里插入图片描述

skiplist,平衡搜索树,哈希表的对比

SkipList(跳表):
查找效率:跳表的平均时间复杂度为O(logN),其中N是节点的数量。这是因为跳表通过多层链表结构,使得查找过程能够跳过部分节点,从而加快查找速度;
特点:跳表是一种概率平衡的数据结构,其实现相对简单,且支持快速的插入,删除和查找操作。同时,跳表在内存中的占用也相对较低

平衡搜索树
查找效率:平衡搜索树的查找时间复杂度通常我O(logN),其中N是节点的数量。这是因为平衡搜索树通过旋转,分裂等操作来保持树的平衡性,从而确保每次查找都能快速定位到目标节点
特定:平衡搜索树具有严格的平衡性要求,使其插入,删除和查找操作都能保持较高的效率。同时,平衡搜索树还支持范围查询等复杂操作。然而,其实现相对复杂,且需要额外的空间来维护树的平衡性

哈希表
查找效率:哈希表的查找效率时间复杂度平均为O(1),但在最坏情况下可能退化为O(N)。因此,哈希表的查找效率取决于哈希函数的优劣和装填因子的设置
特点:哈希表具有极高的查找效率,特别适用于需要频繁查找的场景。同时,哈希表的插入和删除操作也相对简单。然而,哈希表不支持范围查询等复杂操作,且哈希冲突较多时,其性能可能会受到影响。此外,哈希表还需要额外的空间来存储哈希函数和链表等结构

实现思路

SkiplistNode

struct SkiplistNode {
    int _val;
    vector<SkiplistNode*> _nexts;

    SkiplistNode(int val, int level) :_val(val), _nexts(level, nullptr) {}
};

search 搜索

在这里插入图片描述
假如我们要查找 17 这个节点

先定义 cur 指向头节点,从最顶层开始查找;发现 9 小于 17(要查找元素在该元素后面),改变cur指向,使cur 指向 9节点。
在这里插入图片描述
再从9节点开始查找,发现 21 大于 17(要查找元素在该元素前面),去下面一层查找,发现 17 == 17,找到要查找的元素。
在这里插入图片描述

    bool search(int target) {
        int level = _head._nexts.size() - 1;
        SkiplistNode* cur = &_head;

        while (level >= 0) {
            if (cur->_nexts[level] && cur->_nexts[level]->_val < target) {
                // 向右走,更新 cur 和 level,target 只能在 cur->_nexts[level]->_val 后面
                cur = cur->_nexts[level];
            }
            else if (!cur->_nexts[level] || cur->_nexts[level]->_val > target) {
                // 向下走,target 只能在 cur->_nexts[level]->_val 前面
                level--;
            }
            else {
                // 找到了
                return true;
            }
        }

        return false;
    }

add 增加

在这里插入图片描述
假如我们要增加 18 。
那我们需要分三步完成,1.寻找到新增节点要插入的位置。2.构造新节点。3.链接节点;其实与单链表的新增节点相似,只不过寻找到新增节点要插入的位置由一点不同

寻找新增节点要插入的位置
我们需要一个数组(大小为最大层数),来记录新节点前面的节点。此时 cur 指向头结点,从顶层开始查找,发现 6 < 18(新增节点在6的后面),改变cur,使cur指向6
在这里插入图片描述
cur指向6,从顶层查找,发现6指向nullptr,向下走,prevs在第4层记录6(如果新增节点有4层,6可能是新增节点的前一个节点)。发现 25 > 18(新增节点在25之前),向下走,prevs在第三层记录6(如果新增节点有3层,6可能是新增节点的前一个节点)。发现 9 < 18(新增节点在9后面),cur 指向9。
在这里插入图片描述
cur指向9,从第二层查找,发现 17 < 18(新增节点在17之后),cur指向17。 在这里插入图片描述
cur指向17,从第二层查找,发现 25 > 18(新增节点在25前面),向下走,prevs在第二层记录17(如果新增节点有2层,17可能是新增节点的前一个节点)。从第一次查找,发现 19 > 18(新增节点在19前面),向下走,prevs在第一层记录19(如果新增节点有1层,19可能是新增节点的前一个节点)。此时层数 小于 0,查找结束。17的后面就是新增节点的位置。
在这里插入图片描述

在这里插入图片描述

    vector<SkiplistNode*> searchPrevs(int num) {
        vector<SkiplistNode*> prevs(_maxLevel, &_head);
        int level = _head._nexts.size() - 1;
        SkiplistNode* cur = &_head;
        while (level >= 0) {
            if (!cur->_nexts[level] || cur->_nexts[level]->_val >= num) {
                // 要插入节点,一定在cur->_nexts[level]的前面
                // 向下走,并更新节点
                prevs[level] = cur;
                level--;
            }
            else if (cur->_nexts[level] && cur->_nexts[level]->_val < num) {
                // 要插入节点,一定在cur->_nexts[level]的后面
                // 向右走
                cur = cur->_nexts[level];
            }
        }

        return prevs;
    }

    void add(int num) {
        // 寻找插入位置,并记录前面的节点
        vector<SkiplistNode*> prevs = searchPrevs(num);

        // 构造新节点,随机层数
        int newlevel = RandomLevel();
        SkiplistNode* newnode = new SkiplistNode(num, newlevel);

        // 连接节点
        newlevel--;
        while (newlevel >= 0) {
            newnode->_nexts[newlevel] = prevs[newlevel]->_nexts[newlevel];
            prevs[newlevel]->_nexts[newlevel] = newnode;
            newlevel--;
        }
    }

earse 删除

在这里插入图片描述
假如我们要删除 19。
我们需要4步完成,1.寻找删除节点,并记录前面节点。2.判断是否存在要删除的节点。3.链接节点。4.删除节点
与新增节点操作类似。

经过第一步寻找删除节点,并记录前面节点后,我们只需判断prevs数组中第0层节点的第0层指向的节点是否等于要删除节点即可。

在这里插入图片描述

    vector<SkiplistNode*> searchPrevs(int num) {
        vector<SkiplistNode*> prevs(_maxLevel, &_head);
        int level = _head._nexts.size() - 1;
        SkiplistNode* cur = &_head;
        while (level >= 0) {
            if (!cur->_nexts[level] || cur->_nexts[level]->_val >= num) {
                // 要插入节点,一定在cur->_nexts[level]的前面
                // 向下走,并更新节点
                prevs[level] = cur;
                level--;
            }
            else if (cur->_nexts[level] && cur->_nexts[level]->_val < num) {
                // 要插入节点,一定在cur->_nexts[level]的后面
                // 向右走
                cur = cur->_nexts[level];
            }
        }

        return prevs;
    }

    bool erase(int num) {
        // 寻找删除节点,并记录前面的节点
        vector<SkiplistNode*> prevs = searchPrevs(num);

        // 判断是否存在要删除的节点
        if (!prevs[0]->_nexts[0] || prevs[0]->_nexts[0]->_val != num)
            return false;
        // 连接节点
        SkiplistNode* del = prevs[0]->_nexts[0];
        int level = del->_nexts.size() - 1;
        while (level >= 0) {
            prevs[level]->_nexts[level] = del->_nexts[level];
            level--;
        }

        // 删除节点
        delete del;

        return true;
    }

整体代码

leetcode上设计跳表的题
1206.设计跳表

在这里插入图片描述

struct SkiplistNode {
    int _val;
    vector<SkiplistNode*> _nexts;

    SkiplistNode(int val, int level) :_val(val), _nexts(level, nullptr) {}
};

class Skiplist {
    // 本质是一个有序链表
public:
    Skiplist() :_head(-1, _maxLevel) {
        srand((unsigned int)time(nullptr));
    }

    bool search(int target) {
        int level = _head._nexts.size() - 1;
        SkiplistNode* cur = &_head;

        while (level >= 0) {
            if (cur->_nexts[level] && cur->_nexts[level]->_val < target) {
                // 向右走,更新 cur 和 level,target 只能在 cur->_nexts[level]->_val 后面
                cur = cur->_nexts[level];
            }
            else if (!cur->_nexts[level] || cur->_nexts[level]->_val > target) {
                // 向下走,target 只能在 cur->_nexts[level]->_val 前面
                level--;
            }
            else {
                // 找到了
                return true;
            }
        }

        return false;
    }

    void add(int num) {
        // 寻找插入位置,并记录前面的节点
        vector<SkiplistNode*> prevs = searchPrevs(num);

        // 构造新节点,随机层数
        int newlevel = RandomLevel();
        SkiplistNode* newnode = new SkiplistNode(num, newlevel);

        // 连接节点
        newlevel--;
        while (newlevel >= 0) {
            newnode->_nexts[newlevel] = prevs[newlevel]->_nexts[newlevel];
            prevs[newlevel]->_nexts[newlevel] = newnode;
            newlevel--;
        }
    }

    bool erase(int num) {
        // 寻找删除节点,并记录前面的节点
        vector<SkiplistNode*> prevs = searchPrevs(num);

        // 判断是否存在要删除的节点
        if (!prevs[0]->_nexts[0] || prevs[0]->_nexts[0]->_val != num)
            return false;
        // 连接节点
        SkiplistNode* del = prevs[0]->_nexts[0];
        int level = del->_nexts.size() - 1;
        while (level >= 0) {
            prevs[level]->_nexts[level] = del->_nexts[level];
            level--;
        }

        // 删除节点
        delete del;

        return true;
    }

private:
    int RandomLevel() {
        int level = 1;
        while (rand() <= RAND_MAX * _p && level < _maxLevel)
            level++;

        return level;
    }

    vector<SkiplistNode*> searchPrevs(int num) {
        vector<SkiplistNode*> prevs(_maxLevel, &_head);
        int level = _head._nexts.size() - 1;
        SkiplistNode* cur = &_head;
        while (level >= 0) {
            if (!cur->_nexts[level] || cur->_nexts[level]->_val >= num) {
                // 要插入节点,一定在cur->_nexts[level]的前面
                // 向下走,并更新节点
                prevs[level] = cur;
                level--;
            }
            else if (cur->_nexts[level] && cur->_nexts[level]->_val < num) {
                // 要插入节点,一定在cur->_nexts[level]的后面
                // 向右走
                cur = cur->_nexts[level];
            }
        }

        return prevs;
    }

private:
    double _p = 0.5;
    int _maxLevel = 32;
    SkiplistNode _head;
};

总结

以上就是跳表的实现

在这里插入图片描述

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

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

相关文章

ssm基于Web的汽车客运订票系统的设计与实现+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码看文章最下面 需要定制看文章最下面 目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容…

SSM中maven

一&#xff1a;maven的分模块开发 maven分模块就是在多人操作一个项目时将maven模块导入依赖&#xff0c;注意仓库里面没有资源坐标&#xff0c;需要使用install操作下载。 二&#xff1a;maven的依赖管理 pom文件中直接写的依赖叫做直接依赖&#xff0c;直接依赖中用到的依…

如何找到养生生活视频素材?推荐几个优秀网站

今天&#xff0c;我们来聊一个实用的话题&#xff0c;那就是如何找到优质的养生视频素材。作为自媒体创作者&#xff0c;高质量的视频素材对内容制作至关重要。不论你是刚入行的新手&#xff0c;还是已经积累了一定粉丝的大V&#xff0c;找到合适的养生视频素材都能帮助你更好地…

vscode的一些使用心得

问题1&#xff1a;/home目录空间有限 连接wsl或者remote的时候&#xff0c;会在另一端下载一个.vscode-server&#xff0c;vscode的插件都会安装进去&#xff0c;导致空间增加很多&#xff0c;可以选择更换这个文件的位置 参考&#xff1a;https://blog.csdn.net/weixin_4389…

画动态爱心(Python-matplotlib)

介绍 氵而已 由于用的是 AI&#xff0c;注释得非常清楚&#xff0c;自己改改也可以用 代码 # -*- coding: utf-8 -*- # Environment PyCharm # File_name 尝试1 |User Pfolg # 2024/11/05 22:45 import numpy as np import matplotlib.pyplot as plt import matplot…

13-鸿蒙开发中的综合实战:华为登录界面

大家好&#xff0c;欢迎来到鸿蒙开发系列教程&#xff01;今天&#xff0c;我们将通过一个综合实战项目来实现一个华为登录界面。这个项目将涵盖输入框组件、按钮组件、文本组件和布局容器的使用&#xff0c;帮助你更好地理解和应用这些组件。无论你是初学者还是有一定经验的开…

LCL三相并网逆变器simulink仿真+说明文档

背景描述&#xff1a; 详细解析了LCL三相并网逆变器的工作原理&#xff0c;强调了准PR比例谐振控制的重要性&#xff0c;讨论了电感、电容参数选择及保护电路设计。通过仿真结果展示了逆变器性能优化的方法&#xff0c;以提升系统效率和稳定性。 模型介绍&#xff1a; 整体模…

突破1200°C高温性能极限!北京科技大学用机器学习合成24种耐火高熵合金,室温延展性极佳

在工程应用中&#xff0c;如燃气轮机、核反应堆和航空推进系统&#xff0c;对具备优异高温机械性能的金属合金需求十分旺盛。由于材料熔点的固有限制&#xff0c;传统镍基 (Ni) 高温合金的耐温能力已接近极限。为满足开发高温结构材料的需求&#xff0c;耐火高熵合金 (RHEAs) 于…

使用GPT-SoVITS训练语音模型

1.项目演示 阅读单句话 1725352713141 读古诗 1725353700203 2.项目环境 开发环境&#xff1a;linux 机器配置如下&#xff1a;实际使用率百分之二十几&#xff0c; 3.开发步骤 1.首先是准备数据集&#xff0c;要求是wav格式&#xff0c;一到两个小时即可&#xff0c; 2.…

react18中redux-promise搭配redux-thunk完美简化异步数据操作

用过redux-thunk的应该知道&#xff0c;操作相对繁琐一点&#xff0c;dispatch本只可以出发plain object。redux-thunk让dispatch可以返回一个函数。而redux-promise在此基础上大大简化了操作。 实现效果 关键逻辑代码 store/index.js import { createStore, applyMiddlewar…

【JS学习】10. web API-BOM

文章目录 Web APIs - 第5天笔记js组成window对象定时器-延迟函数location对象navigator对象histroy对象本地存储&#xff08;今日重点&#xff09;localStorage&#xff08;重点&#xff09;sessionStorage&#xff08;了解&#xff09;localStorage 存储复杂数据类型 综合案例…

The First项目报告:MANTRA如何实现世界金融区块链化?

RWA&#xff08;现实世界资产&#xff09;代币化被视为加密领域的下一个财富增长点&#xff0c;它作为桥梁连接传统金融与加密世界&#xff0c;潜力覆盖数十万亿美元资产市场。尽管面临技术、监管及市场挑战&#xff0c;RWA项目正逐步获得广泛关注。MANTRA是一个Cosmos SDK基L1…

DAY21|二叉树Part08|LeetCode: 669. 修剪二叉搜索树、108.将有序数组转换为二叉搜索树、538.把二叉搜索树转换为累加树

目录 LeetCode: 669. 修剪二叉搜索树 基本思路 C代码 LeetCode: 108.将有序数组转换为二叉搜索树 基本思路 C代码 LeetCode: 538.把二叉搜索树转换为累加树 基本思路 C代码 LeetCode: 669. 修剪二叉搜索树 力扣代码链接 文字讲解&#xff1a;LeetCode: 669. 修剪二叉搜…

大模型LLama3!!!Ollama下载、部署和应用(保姆级详细教程)

首先呢&#xff0c;大家在网站先下载ollama软件 这就和anaconda和python是一样的 废话不多说 直接上链接&#xff1a;Download Ollama on Windows 三个系统都支持 注意&#xff1a; 这里的Models&#xff0c;就是在上面&#xff0c;大家点开之后&#xff0c;里面有很多模型…

C++转义序列

\b \b是一个退格符&#xff08;backspace character&#xff09;&#xff0c;它的作用是将光标向左移动一个位置&#xff0c;但并不会删除光标位置上的字符。这个行为在某些情况下可能会导致视觉上的字符“消失”&#xff0c;但实际上这些字符仍然存在于输出缓冲区中&#xf…

[渲染层网络层错误] net::ERR_CONTENT_LENGTH_MISMATCH 问题解决

问题描述 问题背景 微信小程序访问后端img资源的时候&#xff0c;偶尔出现这个感叹号&#xff0c;图片加载不出来&#xff0c;但是对应的url贴出来在浏览器中访问&#xff0c;或者重新加载是可以访问的。 错误描述 经查询前端报错 [渲染层网络层错误] net::ERR_CONTENT_LE…

初始JavaEE篇 —— 网络编程(1):基础的网络知识

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 前言&#xff1a; 网络的发展历程 网络通信基础 IP地址 端口号 网络协议 网络通信的流程 前言&#xff1a; 我们现在所…

基于SSM+小程序的高校寻物平台管理系统(失物1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 本基于微信小程序的高校寻物平台有管理员&#xff0c;用户以及失主三个角色。 1、管理员功能有个人中心&#xff0c;用户管理&#xff0c;失主管理&#xff0c;寻物启示管理&#xff0c;拾…

leetcode21:合并两个有序列表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[]示…

【c++丨STL】vector的使用

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C、STL 目录 前言 vector简要介绍 一、vector的默认成员函数 构造函数(constructor) 析构函数(destructor) 赋值运算符重载operator 二、vector的容量接口…