详细讲解设计跳表的三个步骤(查找、插入、删除)

目录

    • 写在前面
    • 跳表概要
    • 查找步骤
    • 插入步骤
    • 删除步骤
    • 完整代码

写在前面

关于跳表的一些知识可以参考这篇文章,最好是先看完这篇文章再看详细的思路->代码的复现步骤:
Redis内部数据结构详解(6)——skiplist
关于跳表的插入、删除基本操作其实也就是链表的插入和删除,所以如果不熟悉的话还得先回顾链表的插入以及删除是怎样的,可以参考:
【数据结构基础笔记】【链表】
相关代码以及其他语言的写法或者其他思路可以参考:
1206. 设计跳表
代码参考链接:
https://leetcode-cn.com/problems/design-skiplist/solution/can-kao-redisshi-xian-by-bakezq/
https://www.iteye.com/blog/kenby-1187303

跳表概要

跳表采用随机法决定链表中哪些节点应该增加向前指针以及在该节点中应增加多少个指针。
跳表结构的头节点需要有足够的指针域,以满足可能构造最大级数的需求,而尾节点不需要指针域。
跳表在原有的有序链表上增加了多级索引,通过索引来实现快速查找。
1、首先在最高级索引上查找最后一个小于当前查找元素的位置
2、调到次高级索引继续查找,直到调到最底层为止,如果查找元素存在的话,此刻已经十分接近要查找的元素的位置了。

查找步骤

你需要了解的内容:

1、指定head为左上角
2、初始化prevs数组(转折点数组)
3、查找只有 right 和 down 两个方向
4、maxlevel是当前跳表的最大层数,并非一开始人为限制的MAXLEVEL。maxlevel可能会随着插入新的结点时的产生随机层数而更新。
但是总是有:maxlevel <= MAXLEVEL

以下面的图为例:从中你应该能清楚了解到prevs数组的含义。
在这里插入图片描述
从从顶层向下遍历到最底层:
在这里插入图片描述
在这里插入图片描述
根据这样的思路可以写出这样的代码:

vector<Node*> _search(int key)
{Node* cur = &head;vector<Node*> prevs(MAX_LEVEL);//从顶层向下遍历,注意这里的maxlevel是当前跳表的最大层数,并非一开始人为限制的MAXLEVEL。//maxlevel可能会随着插入新的结点时的产生随机层数而更新。for(int i = maxlevel- 1;i >= 0;i--){//在当前层中比较,直到查找元素小于keywhile(cur->level[i] && cur->level[i]->val < key)cur = cur->level[i];//每层找到一个最靠近的点,作为向下的转折点prevs[i] = cur;}return prevs;
}bool search(int target)
{//获取第一层(最底层)最靠近key且val小于key的节点Node* prev = _search(target)[0];return prev->level[0] && prev->level[0]->val == target;
}

插入步骤

插入步骤:
1、通过查找子函数获取最底层链表中<=num值的最近的节点,赋给prevs
2、随机产生该节点的层数
首先,每个节点肯定都有第1层指针(每个节点都在第1层链表里)。
如果一个节点有第i层(i>=1)指针(即节点已经在第1层到第i层链表中),那么它有第(i+1)层指针的概率为p。
节点最大的层数不允许超过一个最大值,记为MaxLevel。

static int random_level() {int level = 1;while (rand() < SKIPLIST_P_VAL && level < MAX_LEVEL) level++;return level;
}

randomLevel()的伪码中包含两个参数,一个是p,一个是MaxLevel。在Redis的skiplist实现中,这两个参数的取值为:

p = 1/4
MaxLevel = 32

3、更新当前的跳表中的最大层数maxlevel,在新生成的层里,将prevs[]初始化为head结点:
如下图:当我们插入的数为2,随机出来的层数为5,那么新的一层中小于这个num的节点一定是头结点。
在这里插入图片描述
4、生成一个新的结点,值为num,层数为level
5、对于每一层来说,由于新插入了一个结点,于是原本的索引关系就得发生改变:
将原本prevs的在本层的后继作为cur在本层的后继,再将cur作为prevs的后继:
以第0层为例
在这里插入图片描述
由于这里的prevs[i]都是head结点,所以可以比较方便的看出关系,整理之后就是下面的样子:在这里插入图片描述
代码:

void add(int num)
{//1、通过查找子函数获取最底层链表中<=num值的最近的节点。auto prevs = _search(num);//2、随机产生该节点的层数int level = random_level();//3、更新当前的跳表中的最大层数maxlevel,并且if(level > maxlevel){for(int i = maxlevel; i < level; i++)prevs[i] = &head;maxlevel = level;}Node* cur = new Node(num,level);//for(int i = level -1; i >= 0; i--){cur->level[i] = prevs[i]->level[i];prevs[i]->level[i] = cur;}
}

删除步骤

1、通过查找子函数获取最底层链表中<= num值的最近的节点,赋给prevs
2、特殊情况处理:如果prevs在原链表中不存在(是指向空的节点) 或者 最近的节点的值不等于num(没有值为num的节点),返回错误
3、否则,说明存在值为nums的结点,且为prevs[0]->level[0]
4、接下来就是要通过修改节点之间的后继关系将值为num的前继后继节点相连。
prevs的后继为需要删除的del节点,则将del的后继连接到prevs后面,作为新的prevs的后继
5、将空间释放
6、删除掉一个结点可能需要更新当前最大层数(如果删除的结点是之前层数最多的话)
判断方法:如果头结点maxlevel-1层的结点没有后继,说明本层已经没有其他节点了

bool erase(int num) 
{//1、通过查找子函数获取最底层链表中<= num值的最近的节点,赋给prevsauto prevs = _search(num);//2、特殊情况处理:如果prevs在原链表中不存在(是指向空的节点) 或者 最近的节点的值不等于num(没有值为num的节点),返回错误if (!prevs[0]->level[0] || prevs[0]->level[0]->val != num)return false;//3、否则,说明存在值为nums的结点,且为prevs[0]->level[0]   Node * del = prevs[0]->level[0];//4、接下来就是要通过修改节点之间的后继关系将值为num的前继后继节点相连。for (int i = 0; i < maxlevel; i++)//prevs的后继为需要删除的del节点,则将del的后继连接到prevs后面,作为新的prevs的后溪if (prevs[i]->level[i] == del)prevs[i]->level[i] = del->level[i];//将空间释放delete del;//删除掉一个结点可能需要更新当前最大层数(如果删除的结点是之前层数最多的话)//如果头结点maxlevel-1层的结点没有后继,说明本层已经没有其他节点了。while (maxlevel > 1 && !head.level[maxlevel - 1])maxlevel--;return true;
}

完整代码

将成员数据和函数进行了私有公有的划分:

class Skiplist {
private://定义结点struct Node {int val;vector<Node *> level;Node(int val, int size = MAX_LEVEL) : val(val), level(size) {}};//定义随机结点层数的参数static const int SKIPLIST_P_VAL = RAND_MAX / 4, MAX_LEVEL = 32;//定义头结点Node head;//定义当前最大层数int maxlevel = 1;//定义search子函数vector<Node*> _search(int key) {Node* cur = &head;vector<Node *> prevs(MAX_LEVEL);// through every level, from top to bottomfor (int i = maxlevel - 1; i >= 0; i--) {// through elements in the current level with smaller valuewhile (cur->level[i] && cur->level[i]->val < key)cur = cur->level[i];prevs[i] = cur;}return prevs;}//定义随机产生层数子函数static int random_level() {int level = 1;while (rand() < SKIPLIST_P_VAL && level < MAX_LEVEL) level++;return level;}public://初始化跳表的时候也需要初始化headSkiplist() : head(INT_MIN, MAX_LEVEL) {}bool search(int target) {Node* prev = _search(target)[0];return prev->level[0] && prev->level[0]->val == target;}void add(int num) {auto prevs = _search(num);int level = random_level();if (level > maxlevel) {for (int i = maxlevel; i < level; i++)prevs[i] = &head;maxlevel = level;}Node * cur = new Node(num, level);for (int i = level - 1; i >= 0; i--) {cur->level[i] = prevs[i]->level[i];prevs[i]->level[i] = cur;}}bool erase(int num) {auto prevs = _search(num);if (!prevs[0]->level[0] || prevs[0]->level[0]->val != num)return false;Node * del = prevs[0]->level[0];for (int i = 0; i < maxlevel; i++)if (prevs[i]->level[i] == del)prevs[i]->level[i] = del->level[i];delete del;while (maxlevel > 1 && !head.level[maxlevel - 1])maxlevel--;return true;}
};/*** Your Skiplist object will be instantiated and called as such:* Skiplist* obj = new Skiplist();* bool param_1 = obj->search(target);* obj->add(num);* bool param_3 = obj->erase(num);*/

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

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

相关文章

php 类静态变量 和 常量消耗内存及时间对比

在对类执行100w次循环后&#xff0c; 常量最快&#xff0c;变量其次&#xff0c;静态变量消耗时间最高 其中&#xff1a; 常量消耗&#xff1a;101.1739毫秒 变量消耗&#xff1a;2039.7689毫秒 静态变量消耗&#xff1a;4084.8911毫秒 测试代码&#xff1a; class Timer_profi…

一个机器周期 计算机_计算机科学组织| 机器周期

一个机器周期 计算机机器周期 (Machine Cycle) The cycle during which a machine language instruction is executed by the processor of the computer system is known as the machine cycle. If a program contains 10 machine language instruction, 10 separate machine …

四、Transforms

transform是torchvision下的一个.py文件&#xff0c;这个python文件中定义了很多的类和方法&#xff0c;主要实现对图片进行一些变换操作 一、Transforms讲解 from torchvision import transforms#按着Ctrl&#xff0c;点击transforms进入到__init__.py文件中 from .transfo…

leetcode 134. 加油站 思考分析

目录题目1、暴力法&#xff0c;双层遍历2、贪心题目 在一条环路上有 N 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0…

单链线性表的实现

//函数结果状态代码#define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #define OVERFLOW -2 //Status是函数的类型&#xff0c;其值是函数结果状态代码 typedef int Status; typedef int ElemType;…

时间模块,带Python示例

Python时间模块 (Python time Module) The time module is a built-in module in Python and it has various functions that require to perform more operations on time. This is one of the best modules in Python that used to solve various real-life time-related pro…

五、torchvision

一、下载CIFAR-10数据集 CIFAR-10数据集官网 通过阅读官网给的解释可以大概了解到&#xff0c;一共6w张图片&#xff0c;每张图片大小为3232&#xff0c;5w张训练图像&#xff0c;1w张测试图像&#xff0c;一共由十大类图像。 CIFAR10官网使用文档 torchvision.datasets.CIF…

leetcode 69. x 的平方根 思考分析

题目 实现 int sqrt(int x) 函数。 计算并返回 x 的平方根&#xff0c;其中 x 是非负整数。 由于返回类型是整数&#xff0c;结果只保留整数的部分&#xff0c;小数部分将被舍去。 示例 1: 输入: 4 输出: 2 示例 2: 输入: 8 输出: 2 说明: 8 的平方根是 2.82842…, 由于返回…

背包问题 小灰_小背包问题

背包问题 小灰Prerequisites: Algorithm for fractional knapsack problem 先决条件&#xff1a; 分数背包问题算法 Here, we are discussing the practical implementation of the fractional knapsack problem. It can be solved using the greedy approach and in fraction…

360浏览器兼容问题

360浏览器兼容问题 360浏览器又是一大奇葩&#xff0c;市场份额大&#xff0c;让我们不得不也对他做些兼容性处理。 360浏览器提供了两种浏览模式&#xff0c;极速模式和兼容模式&#xff0c;极速模式下是webkit内核的处理模式&#xff0c;兼容模式下是与IE内核相同的处理模式。…

转 设计师也需要了解的一些前端知识

一、常见视觉效果是如何实现的 一些事 关于文字效果 互联网的一些事 文字自身属性相关的效果css中都是有相对应的样式的&#xff0c;如字号、行高、加粗、倾斜、下划线等&#xff0c;但是一些特殊的效果&#xff0c;主要表现为ps中图层样式中的效果&#xff0c;css是无能为力的…

六、DataLoader

一、DataLoader参数解析 DataLoader官网使用手册 参数描述dataset说明数据集所在的位置、数据总数等batch_size每次取多少张图片shuffleTrue乱序、False顺序(默认)samplerbatch_samplernum_workers多进程&#xff0c;默认为0采用主进程加载数据collate_fnpin_memorydrop_las…

单调栈 leetcode整理(一)

目录单调栈知识402. 移掉K位数字1673. 找出最具竞争力的子序列316. 去除重复字母&#xff08;1081. 不同字符的最小子序列&#xff09;321. 拼接最大数单调栈知识 单调栈就是一个内部元素有序的栈&#xff08;大->小 or 小->大&#xff09;&#xff0c;但是只用到它的一…

数字签名 那些密码技术_密码学中的数字签名

数字签名 那些密码技术A signature is usually used to bind signatory to the message. The digital signature is thus a technique that binds a person or the entity to the digital data. This binding ensures that the person sending the data is solely responsible …

七、torch.nn

一、神经网络模块 进入到PyTorch的torch.nnAPI学习页面 PyTorch提供了很多的神经网络方面的模块&#xff0c;NN就是Neural Networks的简称 二、Containers torch.nn下的Containers 一共有六个模块&#xff0c;最常用的就是Module模块&#xff0c;看解释可以知道&#xff0c…

Java多线程初学者指南(8):从线程返回数据的两种方法

本文介绍学习Java多线程中需要学习的从线程返回数据的两种方法。从线程中返回数据和向线程传递数据类似。也可以通过类成员以及回调函数来返回数据。原文链接 从线程中返回数据和向线程传递数据类似。也可以通过类成员以及回调函数来返回数据。但类成员在返回数据和传递数据时有…

【C++进阶】 遵循TDD原则,实现平面向量类(Vec2D)

目录1、明确要实现的类的方法以及成员函数2、假设已经编写Vec2D&#xff0c;根据要求&#xff0c;写出测试代码3、编写平面向量类Vec2D,并进行测试4、完整代码5、最终结果1、明确要实现的类的方法以及成员函数 考虑到效率问题&#xff0c;我们一般将函数的参数设置为引用类型。…

Keilc的中断号计算方法

中断号码 (中断向量-3)/8转载于:https://www.cnblogs.com/yuqilihualuo/p/3423634.html

md5模式 签名_MD的完整形式是什么?

md5模式 签名医师&#xff1a;医学博士/常务董事 (MD: Doctor of Medicine / Managing Director) 1)医学博士&#xff1a;医学博士 (1) MD: Doctor of Medicine) MD is an abbreviation of a Doctor of Medicine degree. In the field of Medicine, it is the main academic de…

八、卷积层

一、Conv2d torch.nn.Conv2d官网文档 torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue, padding_modezeros, deviceNone, dtypeNone) 参数解释官网详情说明in_channels输入的通道数&#xff0c;如果是彩色照片通道…