跳表 | 基本概念 | 代码实现
文章目录
- 1.跳表的基本概念
- 2.跳表的结构
- 3.跳表的增删改查
- 4.完整代码
1.跳表的基本概念
跳表的本质是一种查找结构,一般查找问题的解法分为两个大类:一个是基于各种平衡树,一个是基于哈希表,跳表比较的特殊,它独成一派。跳表是基于有序链表的基础上发展而来的,普通链表查找只能一个一个往下跳,而跳表能一次跳过好几个结点,这就是它查找效率高的原因。 例如:
如果只有一层那么查找就是逐个遍历,效率就是O(n)
如果为两个相邻结点添加第二层指向
甚至是添加第三层的指向
每层的结点个数呈现2:1的对应关系,查找就是从最高层开始,依次往下查找,这个过程类型二分查找,效率可以来到O(log n)。但是如果严格维持这种2:1的对应关系,插入删除节点都需要重新调整,插入删除效率直接就降到O(n)。所以跳表采用随机化结点的层数,来控制查找插入删除的时间复杂度近似为O(log n)。怎么计算可以参考博客。
2.跳表的结构
跳表的结点有多层,通过vector<SkiplistNode*> 来存储下一个结点的指针。初始化跳表时是带头结点的,它的层数要求是最高的,它开始只有它自己,默认给一层,后面插入的时候如果有结点的层数高于它,需要调整。
struct SkiplistNode
{ int _data;vector<SkiplistNode*> _nextV;SkiplistNode(int data,int level):_data(data),_nextV(level,nullptr){}
};class Skiplist
{typedef SkiplistNode Node;
private:Node* _head;int _maxLevel = 32;double _p = 0.25;public:Skiplist(){_head = new Node(-1,1);}
};int main()
{Skiplist list; return 0;
}
3.跳表的增删改查
看看自己的代码有没有问题可以在leetcode上提交代码:题目链接
1)跳表的查找: 跳表中的元素是有序的,在理解跳表查找前先来看一下一个有序的单链表是如何查找元素的。
查找有序单链表,cur表示当前结点,如果cur的值data等于待查找的值target说明找到了。可如果cur 的下一个结点的值data大于target,说明target目标值不在链表中,或者是链表遍历到结尾还是找不到也说明target目标值不在链表中,这就是有序单链表的查找过程。
有了有序单链表的查找的基础,来看跳表的查找就简单了。
1、cur表示当前结点、next表示下一个结点、data表示结点的数据。
2、要从最高层找起,直到遍历到最低的那一层。
3、如果target 等于 cur的 data说明找到了。
4、如果target 大于 next 的 data,向右找。但是要保证next 不能为NULL。如找19而当前cur 的data是7,它的next 是NULL。不代表找不到,而是要到下一层找。
5、如果next的date 大于target 说明target可能在cur 和 next之间,往下一层找。
bool search(int target)
{Node* cur = _head;int level = cur->_nextV.size() -1;while(level >= 0){if(cur->_nextV[level] && cur->_nextV[level]->_data < target){cur = cur->_nextV[level];}else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data > target){level--;}else {return true;}}return false;
}
2)跳表的插入: 类比单链表的从中间的某个位置插入。需要找到前一个位置prev。跳表要找的是prevV,前结点指针列表。
1、找到prevV
2、创建一个结点newNode,先让newNode->_nextV[i] 逐个指向 prevV[i]->_nextV[i]指向的结点。再让prevV[i]->_nextV[i]指向newNode。
3、这里创建newNode层数是随机的,需要根据概率计算。这里参照radis中给定的概率和最大层数。
4、如果插入节点的层数高于头结点,需要调整头结点的层数。
vector<Node*> findPervV(int num)
{Node* cur = _head;int level = _head->_nextV.size() -1;vector<Node*> prevV(level + 1,nullptr);while(level >= 0){ if(cur->_nextV[level] != nullptr && cur->_nextV[level]->_data < num){cur = cur->_nextV[level];}else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data >= num){prevV[level] = cur;level--;}}return prevV;
}void add(int num)
{int n = randomLevel();int level = _head->_nextV.size() -1;vector<Node*> prevV = findPervV(num);// 调整头结点的层数if(n > _head->_nextV.size()){_head->_nextV.resize(n,nullptr);prevV.resize(n,_head);}Node* newNode = new Node(num,n); for(int i = 0;i < n;i++){ newNode->_nextV[i] = prevV[i]->_nextV[i];prevV[i]->_nextV[i] = newNode;}
}int randomLevel()
{Node* cur = _head;srand(time(NULL));int level= 1;// rand的区间范围为[0,RAND_MAX]while(rand() <= RAND_MAX*_p && level < _maxLevel) { level++;}return level;
}
3)跳表的删除:
1、要到前结点列表prevV
2、根据当前结点的层数,循环指向prevV[i]->_nextV[i] = del->_nextV[i]
bool erase(int num)
{vector<Node*> prevV = findPervV(num);if(prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_data != num){return false;}else{Node* del = prevV[0]->_nextV[0];// 当前节点的层数for(int i = 0;i < del->_nextV.size();i++){prevV[i]->_nextV[i] = del->_nextV[i];}delete del;int index = _head->_nextV.size() -1;// _head->_nextV[index] == nullptr说明最高结点删除了,调整头结点while (index >= 0) {if(_head->_nextV[index] == nullptr) {index--;}else {break;} }return true;}
}
4)跳表的打印: 这里按照单链表的思路打印,如果只看第一层那么跳表就像是有序的单链表。注意跳表是带头指针的,所以直接从_head->nextV[0]开始。
void print()
{Node* cur = _head->_nextV[0];while(cur != nullptr){ cout << cur->_data << "->"; cur = cur->_nextV[0];}cout << "NULL" << endl;
}
4.完整代码
#include <vector>
#include <iostream>
#include <time.h>
#include <stdlib.h>using namespace std;struct SkiplistNode
{ int _data;vector<SkiplistNode*> _nextV;SkiplistNode(int data,int level):_data(data),_nextV(level,nullptr){}
};class Skiplist
{typedef SkiplistNode Node;
private:Node* _head;int _maxLevel = 32;double _p = 0.25;public:Skiplist(){_head = new Node(-1,1);}bool search(int target){Node* cur = _head;int level = cur->_nextV.size() -1;while(level >= 0){if(cur->_nextV[level] && cur->_nextV[level]->_data < target){cur = cur->_nextV[level];}else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data > target){level--;}else {return true;}}return false;}vector<Node*> findPervV(int num){Node* cur = _head;int level = _head->_nextV.size() -1;vector<Node*> prevV(level + 1,nullptr);while(level >= 0){ if(cur->_nextV[level] != nullptr && cur->_nextV[level]->_data < num){cur = cur->_nextV[level];}else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data >= num){prevV[level] = cur;level--;}}return prevV; }void add(int num){int n = randomLevel();int level = _head->_nextV.size() -1;vector<Node*> prevV = findPervV(num);if(n > _head->_nextV.size()){_head->_nextV.resize(n,nullptr);prevV.resize(n,_head);}Node* newNode = new Node(num,n); for(int i = 0;i < n;i++){ newNode->_nextV[i] = prevV[i]->_nextV[i];prevV[i]->_nextV[i] = newNode;}}int randomLevel(){Node* cur = _head;srand(time(NULL));int level= 1;while(rand() <= RAND_MAX*_p && level < _maxLevel) { level++;}return level;}bool erase(int num){vector<Node*> prevV = findPervV(num);if(prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_data != num){return false;}else{Node* del = prevV[0]->_nextV[0];for(int i = 0;i < del->_nextV.size();i++){prevV[i]->_nextV[i] = del->_nextV[i];}delete del;int index = _head->_nextV.size() -1;while (index >= 0) {if(_head->_nextV[index] == nullptr) {index--;}else {break;} }return true;}}void print(){Node* cur = _head->_nextV[0];while(cur != nullptr){ cout << cur->_data << "->"; cur = cur->_nextV[0];}cout << "NULL" << endl;}};int main()
{Skiplist list; list.add(1);list.add(3);list.add(6);cout << list.search(6) << endl;list.erase(9);list.erase(6);cout << list.search(6) << endl;list.print();return 0;
}
相关文章:
跳表 | 基本概念 | 代码实现
文章目录 1.跳表的基本概念2.跳表的结构3.跳表的增删改查4.完整代码 1.跳表的基本概念 跳表的本质是一种查找结构,一般查找问题的解法分为两个大类:一个是基于各种平衡树,一个是基于哈希表,跳表比较的特殊,它独成一派…...
分数加减
#include <stdio.h> #include <stdlib.h>// 求最大公因数 int gcd(int a, int b) {return b 0? a : gcd(b, a % b); }// 化简分数 void simplify(int *num, int *den) {int g gcd(*num, *den);*num / g;*den / g;if (*den < 0) {*num * -1;*den * -1;} }//…...
基于卷积神经网络的皮肤病识别系统(pytorch框架,python源码,GUI界面,前端界面)
更多图像分类、图像识别、目标检测等项目可从主页查看 功能演示: 皮肤病识别系统 vgg16 resnet50 卷积神经网络 GUI界面 前端界面(pytorch框架 python源码)_哔哩哔哩_bilibili (一)简介 基于卷积神经网络的皮肤病识…...
QT与嵌入式——获取网络实时时间
目录 1、使用QT通过网络API接口获取网络实时时间 1.1、首先在网上找一个获取实时时间的API接口 1.2、 根据第一步获取的链接来发送请求 1.3、通过connect链接信号与槽 注意的点: 2、为什么需要网络实时时间 3、获取本机的实时时间 4、顺带提一句 1、使用QT通过…...
优化装配,提升品质:虚拟装配在汽车制造中的关键作用
汽车是各种零部件的有机结合体,因此汽车的装配工艺水平和装配质量直接影响着汽车的质量与性能。在汽车装配过程中,经常会发生零部件间干涉或装配顺序不合理等现象,且许多零部件制造阶段产生的质量隐患要等到实际装配阶段才能显现出来…...
Bug的严重等级和优先级别与分类
目录 前言 1. Bug的严重等级定义 2.Bug的优先等级 3.一般 BUG 的正规的处理流程 4.BUG严重等级划分 5.BUG紧急程度定义 前言 Bug是指在软件开发或者系统运行过程中出现的错误、缺陷或者异常情况。它可能导致系统无法正常工作、功能不完整、数据错误或者界面异常等问题。 …...
游戏引擎学习第13天
视频参考:https://www.bilibili.com/video/BV1QQUaYMEEz/ 改代码的地方尽量一张图说清楚吧,懒得浪费时间 game.h #pragma once #include <cmath> #include <cstdint> #include <malloc.h>#define internal static // 用于定义内翻译单元内部函数 #…...
bind返回失败(ctrl+c)结束后不能再次加载
问题现象(VxWorks): 在测试的时候发现使用ctrlc打断程序后再次调用bind绑定失败 错误返回 0x30 问题分析: 1、程序没有开启端口复用。 2、程序在使用ctrlc打断后 vxWorks的打断和linux不相同,并没有清除底层的端口&a…...
菜鸟驿站二维码/一维码 取件识别功能
特别注意需要引入 库文 ZXing 可跳转: 记录【WinForm】C#学习使用ZXing.Net生成条码过程_c# zxing-CSDN博客 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static System.Net.…...
23种设计模式-备忘录(Memento)设计模式
文章目录 一.什么是备忘录设计模式?二.备忘录模式的特点三.备忘录模式的结构四.备忘录模式的优缺点五.备忘录模式的 C 实现六.备忘录模式的 Java 实现七.总结 类图: 备忘录设计模式类图 一.什么是备忘录设计模式? 备忘录设计模式(…...
搜维尔科技:Manus遥操作五指机械手专用手套惯性高精度虚拟现实
Manus遥操作五指机械手专用手套惯性高精度虚拟现实 搜维尔科技:Manus遥操作五指机械手专用手套惯性高精度虚拟现实...
MySql面试题.运维面试题之五
《(全国)MySQL数据库DBA测试题-第1套》 卷面总分 题号 单选题 多选题 判断题 100 题分 42 40 18 得分 一、单选题(每题3分,共计42分;得分____) 1. 二进制rpm包安装的mysql数据库,默认的数据文件存放在如下哪个目录里? A、/usr/local/mysql B、/tmp/ C、/var/lib/my…...
小程序-基于java+SpringBoot+Vue的小区服务管理系统设计与实现
项目运行 1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境:Tomcat 7.x,8.x,9.x版本均可 4.硬件环境:…...
JWT 过期后 自动刷新方案
JWT(JSON Web Token)广泛应用于现代 Web 开发中的认证与授权,它以无状态、灵活和高效的特点深受开发者欢迎。然而,JWT 的一个核心问题是 Token 过期后如何处理。本文将总结常见的解决方案,分析其优缺点,并帮…...
react-amap海量点优化
前言:高版本的react-amap 支持MassMarkers 组件用于一次性添加大量的标记点。本次优化的海量点是在低版本react-amap的基础上。官方推荐使用聚合useCluster属性来优化海量点的渲染。 直接附上代码: import React, { Component } from "react"…...
GRU(门控循环单元)详解
1️⃣ GRU介绍 前面介绍的LSTM可以有效缓解RNN的梯度消失问题,但是其内部结构比较复杂,因此衍生出了更加简化的GRU。GRU把输入门和遗忘门整合成一个更新门,并且合并了细胞状态和隐藏状态。于2014年被提出 2️⃣ 原理介绍 GRU的结构和最简单…...
【代码随想录|回溯算法排列问题】
491.非减子序列 题目链接. - 力扣(LeetCode) 这里和子集问题||很像,但是这里要的是非递减的子序列,要按照给的数组的顺序来进行排序,就是如果我给定的数组是[4,4,3,2,1],如果用子集||的做法先进行排序得到…...
Azure Kubernetes Service (AKS)资源优化策略
针对Azure Kubernetes Service (AKS)的资源优化策略,可以从多个维度进行考虑和实施,以提升集群的性能、效率和资源利用率。以下是一些关键的优化策略: 一、 Pod资源请求和限制 设置Pod请求和限制:在YAML清单中为所有Pod设置CPU和…...
R语言 | 宽数据变成一列,保留对应的行名和列名
对应稀疏矩阵 转为 宽数据框,见 数据格式转换 | 稀疏矩阵3列还原为原始矩阵/数据框,自定义函数 df3toMatrix() 目的:比如查看鸢尾花整体的指标分布,4个指标分开,画到一个图中。每个品种画一个图。 1.数据整理&#…...
RTSP播放器EasyPlayer.js播放器在webview环境下,PC和安卓能够正常播放,IOS环境下播放器会黑屏无法播放
流媒体技术分为顺序流式传输和实时流式传输两种。顺序流式传输允许用户在下载的同时观看,而实时流式传输则允许用户实时观看内容。 流媒体播放器负责解码和呈现内容,常见的播放器包括VLC和HTML5播放器等。流媒体技术的应用场景广泛,包括娱乐…...
.NET周刊【11月第3期 2024-11-17】
国内文章 .NET 9使用Scalar替代Swagger https://www.cnblogs.com/netry/p/18543378/scalar-an-alternative-to-swagger-in-dotnet-9 .NET 9 移除了 Swashbuckle.AspNetCore,因为其维护不力,并转向 Microsoft.AspNetCore.OpenApi。除了 Swashbuckle&am…...
c语言数据22数组使用
1.1数组分配的空间 int a[10]{1,2,3,4,5,6,7,8,9,10};//分配空间 元素类型大小int4*元素个数1040byte 元素之间空间连续 数组名代表数组首元素地址;a 取的是a[0]的地址;&a 是整个数组的地址 说明: 数组首元素地址: 0号元…...
深入理解TensorFlow中的形状处理函数
摘要 在深度学习模型的构建过程中,张量(Tensor)的形状管理是一项至关重要的任务。特别是在使用TensorFlow等框架时,确保张量的形状符合预期是保证模型正确运行的基础。本文将详细介绍几个常用的形状处理函数,包括get_…...
MySQL数据库3——函数与约束
一.函数 1.字符串函数 MySQL中内置了很多字符串函数,常用的几个如下: 使用方法: SELECT 函数名(参数);注意:MySQL中的索引值即下标都是从1开始的。 2.数值函数 常见的数值函数如下: 使用方法: SELECT…...
⾃动化运维利器 Ansible-Jinja2
Ansible-Jinja2 一、Ansible Jinja2模板背景介绍二、 JinJa2 模板2.1 JinJa2 是什么2.2 JinJa2逻辑控制 三、如何使用模板四、实例演示 按顺序食用,口味更佳 ( 1 ) ⾃动化运维利器Ansible-基础 ( 2 ) ⾃动化运维利器 Ansible-Playbook ( 3 ) ⾃动化运维利器 Ansible…...
博客文章怎么设计分类与标签
首发地址(欢迎大家访问):博客文章怎么设计分类与标签 新网站基本上算是迁移完了,迁移之后在写文章的过程中,发现个人的文章分类和标签做的太混乱了,分类做的像标签,标签也不是特别的丰富&#x…...
FastDDS之DataSharing
目录 原理说明限制条件配置Data-Sharing delivery kindData-sharing domain identifiers最大domain identifiers数量共享内存目录 DataReader和DataWriter的history耦合DataAck阻塞复用 本文详细记录Fast DDS中Data Sharing的实现原理和代码分析。 DataSharing的概念࿱…...
计算机网络在线测试-概述
单项选择题 第1题 数据通信中,数据传输速率(比特率,bps)是指每秒钟发送的()。 二进制位数 (我的答案) 符号数 字节数 码元数 第2题 一座大楼内的一个计算机网络系统…...
【MySQL】数据库必考知识点:查询操作全面详解与深度解剖
前言:本节内容讲述基本查询, 基本查询要分为两篇文章进行讲解。 本篇文章主要讲解的是表内删除数据、查询结果进行插入、聚合统计、分组聚合统计。 如果想要学习对应知识的可以观看哦。 ps:本篇内容友友们只要会创建表了就可以看起来了哦!&am…...
鲸鱼机器人和乐高机器人的比较
鲸鱼机器人和乐高机器人各有其独特的优势和特点,家长在选择时可以根据孩子的年龄、兴趣、经济能力等因素进行综合考虑,选择最适合孩子的教育机器人产品。 优势 鲸鱼机器人 1)价格亲民:鲸鱼机器人的产品价格相对乐高更为亲民&…...
dedecms手机网站制作/北京网站优化服务
分布式拒绝服务(DDoS:Distributed Denial of Service)攻击指借助于客户/服务器技术,将多个计算机联合起来作为攻击平台,对一个或多个目标发动DoS攻击,从而成倍地提高拒绝服务攻击的威力。通常,攻击者使用一个偷窃帐号将DDoS主控程…...
网站制作 公开课/广州营销型网站
序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。Python有6个序列的内置类型,但最常见的是列表和元组。序列都可以进行的操作包括索引&…...
网站如何做微信支付宝/北京seo顾问服务公司
一、基本概念单例模式属于创建型设计模式。确保一个类只有一个实例,并提供该实例的全局访问点。实现: 使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。二、结构类图:私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有…...
小型网站建设实训教程/百度seo sem
首先到sun下载最新的jmf,然后安装。http://java.sun.com/products/java-media/jmf/index.jsp 然后,说一下需求 1. 用摄像头拍照 2. 在文本框输入文件名 3. 按下拍照按钮,获取摄像头内的图像…...
中国建设银行网站晋阳支行/市场调研的四个步骤
JavaBigdata学习路线 2019-05-28 07:04:33 Auther:MrZhangxd STAGE 第一阶段:JAVA基础 |-第一阶段:JAVA基础 | |-可掌握的核心能力 | | |-熟练掌握常用数据结构和算法,打基础; | | |-全面了解面向对象的编程思想,精通常…...
潍坊网站建设 潍坊做网站/百度访问量统计
接着昨天的继续谈关于微信新出的这个js框架,今天主要谈一个页面的创建到布局的详细步骤。 一.创建一个完整页面 页面你可以创建在项目的任何节点,只要你在入口文件正确引入创建该页面的路径就可使用。 上面使用红色矩形包含的目录,是我新增的…...