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

目录

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

写在前面

关于跳表的一些知识可以参考这篇文章,最好是先看完这篇文章再看详细的思路->代码的复现步骤:
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…

五、torchvision

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

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

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

六、DataLoader

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

七、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;我们一般将函数的参数设置为引用类型。…

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;如果是彩色照片通道…

【C++grammar】左值、右值和将亡值

目录C03的左值和右值C11的左值和右值将亡值在C03中就有相关的概念 C03的左值和右值 通俗的理解&#xff1a; (1) 能放在等号左边的是lvalue (2) 只能放在等号右边的是rvalue (3) lvalue可以作为rvalue使用 对于第三点可以举个例子&#xff1a; int x ; x 6; //x是左值&#…

九、池化层

一、Pooling layers Pooling layers官网文档 MaxPool最大池化层下采样 MaxUnpool最大池化层上采样 AvgPool最大池化层平均采样 例如&#xff1a;池化核为(3,3)&#xff0c;输入图像为(5,5)&#xff0c;步长为1&#xff0c;不加边 最大池化就是选出在池化核为单位图像中的最大…

十、非线性激活函数

一、ReLU torch.nn.ReLU(inplaceFalse)官网提供的API 其中inplace表示是否在对原始数据进行替换 由函数图可以看出&#xff0c;负数通过ReLU之后会变成0&#xff0c;正数则不发生变化 例如&#xff1a;input -1&#xff0c;若inplace True&#xff0c;表示对原始输入数据进…

最短公共子序列_最短公共超序列

最短公共子序列Problem statement: 问题陈述&#xff1a; Given two strings, you have to find the shortest common super sequence between them and print the length of the super sequence. 给定两个字符串&#xff0c;您必须找到它们之间最短的公共超级序列&#xff0c…

QTP自传之web常用对象

随着科技的进步&#xff0c;“下载-安装-运行”这经典的三步曲已离我们远去。web应用的高速发展&#xff0c;改变了我们的思维和生活习惯&#xff0c;同时也使web方面的自动化测试越来越重要。今天&#xff0c;介绍一下我对web对象的识别&#xff0c;为以后的对象库编程打下基础…

leetcode中使用c++需要注意的点以及各类容器的初始化、常用成员函数

目录1、传引用2、vector使用初始化方法常用成员函数3、字符串string初始化方法常用成员函数4、哈希表 unordered_map初始化常用成员函数示例&#xff1a;计数器5、哈希集合 unordered_set初始化常用成员函数6、队列 queue初始化成员函数7、栈stack初始化常用成员函数7、emplace…

十一、线性层

一、Linear Layers torch.nn.Linear(in_features, out_features, biasTrue, deviceNone, dtypeNone) 以VGG神经网络为例&#xff0c;Linear Layers可以将特征图的大小进行变换由(1,1,4096)转换为(1,1,1000) 二、torch.nn.Linear实战 将CIFAR-10数据集中的测试集二维图像[6…

leetcode 42. 接雨水 思考分析(暴力、动态规划、双指针、单调栈)

目录题目思路暴力法动态规划双指针法单调栈题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组…

十二、Sequential

一、Sequential介绍 torch.nn.Sequential(*args) 由官网给的Example可以大概了解到Sequential是将多层网络进行便捷整合&#xff0c;方便可视化以及简化网络复杂性 二、复现网络模型训练CIFAR-10数据集 这里面有个Hidden units隐藏单元其实就是连个线性层 把隐藏层全部展开整…