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

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《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.mzph.cn/news/884951.shtml

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

相关文章

Rancher的安装

1. 概览 1.1 用户界面优势 Rancher 提供了一个直观的图形用户界面&#xff08;GUI&#xff09;。对于不熟悉 Kubernetes 复杂的命令行操作&#xff08;如使用kubectl&#xff09;的用户来说&#xff0c;通过 Rancher 的界面可以方便地进行资源管理。例如&#xff0c;用户可以在…

文件上传和下载

目录 一、准备工作 二、文件上传 三、文件下载 一、准备工作 如果想使用Spring的文件上传功能&#xff0c;则需要再上下文中配置MultipartResolver前端表单要求&#xff1a;为了能上传文件&#xff0c;必须将表单的method设置为post&#xff0c;并将enctype设置为multipart…

Docker 镜像拉不动?自建 Docker Hub 加速站 解决镜像拉取失败

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 众所周知&#xff0c;6 月份的时候&#xff0c;Docker Hub 的镜像就已经无法正常拉取&#xff0c;那会随手用 Nginx 反代了一下 Docker Hub&#xff0c;建了个自用的镜像站&#xff0c;一直用到了 9 月份&…

真·香!深度体验 zCloud 数据库云管平台 -- DBA日常管理篇

点击蓝字 关注我们 zCloud 作为一款业界领先的数据库云管平台&#xff0c;通过云化自治的部署能力、智能巡检和诊断能力、知识即代码的沉淀能力&#xff0c;为DBA的日常管理工作带来了革新式的简化与优化。经过一周的深度体验&#xff0c;今天笔者与您深入探讨 zCloud 在数据库…

Qt的程序如何打包详细教学

生成Release版的程序 在打包Qt程序时&#xff0c;我们需要将发布程序需要切换为Release版本&#xff08;Debug为调试版本&#xff09;&#xff0c;编译器会对生成的Release版可执行程序进行优化&#xff0c;使生成的可执行程序会更小。 debug版本 debug版本是一种开发过程中的…

适配器模式:类适配器与对象适配器

适配器模式是一种结构性设计模式&#xff0c;旨在将一个接口转换成客户端所期望的另一种接口。它通常用于解决由于接口不兼容而导致的类之间的通信问题。适配器模式主要有两种实现方式&#xff1a;类适配器和对象适配器。下面&#xff0c;我们将详细探讨这两种方式的优缺点及适…

语音识别:docker部署FunASR以及springboot集成funasr

内容摘选自: https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_offline_zh.md FunASR FunASR是一个基础语音识别工具包&#xff0c;提供多种功能&#xff0c;包括语音识别&#xff08;ASR&#xff09;、语音端点检测&#xff08;VAD&#xf…

oracle-函数-NULLIF (expr1, expr2)的妙用

【语法】NULLIF (expr1, expr2) 【功能】expr1和expr2相等返回NULL&#xff0c;不相等返回expr1经典的使用场景&#xff1a; 1. 数据清洗与转换 在数据清洗过程中&#xff0c;NULLIF 函数可以用于将某些特定值&#xff08;通常是无效或不需要的值&#xff09;替换为 NULL&…

【LLM】Agentic Workflow的四种常见思路

note Reflection 和 Tool Use 属于比较经典且相对已经广泛使用的方式&#xff0c;Planning 和 Multi-agent 属于比较新颖比较有前景的方式。 文章目录 note一、四种设计模式1. Reflection2. Tool use3. Planning4. Multi-agent collaboration 二、相关代码实践 一、四种设计模…

Python数据可视化seaborn

产品经理在做数据分析时可能需要通过可视化来分析。seaborn官网 1. relplot 散点图 https://seaborn.pydata.org/examples/scatterplot_sizes.html import pandas as pd import seaborn as sns df pd.DataFrame({x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],y: [8, 6, 7, 8, 4, 6,…

基于ssm的个人健康管理系统

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

CSS3新增渐变(线性渐变、径向渐变、重复渐变)

1.线性渐变 代码&#xff1a; 效果图&#xff1a; 使文字填充背景颜色&#xff1a; 效果图&#xff1a; 2.径向渐变 代码&#xff1a; 效果图&#xff1a; 代码图&#xff1a; 效果图&#xff1a; 3.重复渐变 代码&#xff1a; 效果图&#xff1a;

[mysql]mysql的DML数据操作语言增删改,以及新特性计算列,阿里巴巴开发手册mysql相关

1DML数据操作语言,增加删除改数据 插入数据INSERT 插入添加数据,两种方法 方式1:VALUES添加数据 #准备工作 USE atguigudb; CREATE TABLE IF NOT EXISTS emp1( id INT, name VARCHAR(15), hire_data DATE, salary DOUBLE(10,2)); SELECT * FROM emp1 INSERT INTO em…

自由学习记录(19)

unity核心也算是看完了吧&#xff0c;但觉得的确是少了点东西&#xff0c;之后再看mvc框架&#xff0c;和网络开发&#xff0c;&#xff0c;感觉有必要想想主次顺序了&#xff0c;毕竟在明年的3月之前尽量让自己更有贴合需求的能力 先了解一些相关概念&#xff0c;不用看懂&am…

vue计算属性

概念&#xff1a;基于现有的数据&#xff0c;计算出来新属性。并依赖数据的变化&#xff0c;自动重新计算 使用场景&#xff1a; 语法&#xff1a;声明在computed配置项中&#xff0c;一个计算属性对应一个函数&#xff0c;使用起来和普通属性一样使用{{计算属性名}} 代码&…

springboot2.x使用SSE方式代理或者转发其他流式接口

文章目录 1.需求描述2.代码2.1.示例controller2.2.示例service2.3.示例impl 3.测试 1.需求描述 使用SSE的方式主要还是要跟前端建立一个EventSource的链接&#xff0c;有了这个连接&#xff0c;然后往通道里写入数据流&#xff0c;前端自然会拿到流式数据&#xff0c;写啥拿啥…

Hive操作库、操作表及数据仓库的简单介绍

数据仓库和数据库 数据库和数仓区别 数据库与数据仓库的区别实际讲的是OLTP与OLAP的区别 操作型处理(数据库)&#xff0c;叫联机事务处理OLTP&#xff08;On-Line Transaction Processing&#xff09;&#xff0c;也可以称面向用户交易的处理系统&#xff0c;它是针对具体业务…

Ubuntu22.04 安装图形界面以及XRDP教程

一、准备环境 1.一台服务器安装系统ubuntu&#xff08;这里大部分ubuntu系统可以同用&#xff09; 2.安装的ubuntu系统未安装图形界面 二、操作步骤 1.远程ssh或者直接登录服务器命令行界面 ssh -p 远程端口 rootIP 2.更新系统软件包 sudo apt update # 更新本地的软件包…

C++:多态中的虚/纯虚函数,抽象类以及虚函数表

我们在平时&#xff0c;旅游或者是坐高铁或火车的时候。对学生票&#xff0c;军人票&#xff0c;普通票这些概念多少都有些许耳闻。而我们上篇文章也介绍过了继承与多继承。如果这些票我们都分别的去写一个类&#xff0c;当然很冗余&#xff0c;这里我们便可以去使用继承&#…

【易售校园二手平台】开源说明(包含项目介绍、界面展示与系列文章集合)

文章目录 仓库项目介绍技术架构界面登录界面首页闲置商品发布商品详情收藏页面消息页面私聊我的查看我发布的商品 可优化点开发讲解文章集合 仓库 &#x1f3e0;️ 项目仓库&#xff1a;易售校园二手平台gitee仓库 &#x1f30d;️ 在线体验&#xff1a;易售校园二手平台&…