跳表SkipList介绍与实现

article/2025/8/7 21:50:01

目录

一.跳表介绍

二.实现思路

(一).结点结构

(二).检索

(三).插入

(四).删除

三.实现代码


一.跳表介绍

跳表是一种随机化数据结构,主要用于快速检索数据。实质上是一种可以进行二分查找的有序链表。时间复杂度可以达到O(log^n)。在性能上与红黑树、AVL树相当。当然因为结构具有随机性,最坏情况下时间复杂度为O(n)。

跳表结构如下图:

与普通链表相比,跳表每个结点有不止一个指向后续的指针,具体数量是随机出来的。这些指针结构上从低到高排列指向后面与自己同层的指针所在的结点

检索数据时,从head结点开始,按指针从高到低的所指元素大小进行比较,直到找到或走到结尾。因为层数越高代表跳过的元素数量越多,因此理论上可以类比二分查找。

查找时可能找到也可能找不到元素:

如果找到元素,以上图为例,假如要检索的是9,那么顺序如下:

 

 

如果是未找到元素,以6为例:


 

 

 

 

二.实现思路

(一).结点结构

结点通过结构体封装即可,内部是保存的结点元素值和可变数组,数组每一个元素是指向结点的指针。代码如下:

struct SkiplistNode {//结点int _val;//元素值,没有使用模板,可以自定义模板vector<SkiplistNode*> _skipPoints;//结点指针数组SkiplistNode(int val, int n = 1)//n:指针层数,默认1层:_val(val), _skipPoints(n, nullptr){}~SkiplistNode(){for (auto* p : _skipPoints) p = nullptr;}
};

(二).检索

按照上述检索思路,检索失败的标准是走到结点指针的-1层。每一次检索时判断此时同层的指针所指后续元素大小,大于就走到该后续元素,小于就走到低一层的指针。

代码结构如下:

bool search(int target) {Node* cur = _head;//记录当前结点位置int sub = cur->_skipPoints.size() - 1;//从最高层开始,head层数即最高层数while (sub >= 0) {if (没有走到null && 大于后续结点值){cur = cur->_skipPoints[sub];}else if (走到null || 小于后续结点值){sub--;}else 找到结点}没找到结点
}

(三).插入

插入元素前,需要确定在哪个结点后插入,但基于跳表结点多层指针结构,每一层指针的前序指针可能不同,因此需要先检索一遍,确定每一层的前序元素结点

以8为例,插入后,每一层的前序指针不同。

 通过数组记录每一层的前序结点,在插入时按照链表的插入方式插入即可。

插入代码结构如下:

//获取前序结点数组,结构与search相似
vector<Node*> getPrev(int target) {vector<Node*> prev(_head->_skipPoints.size(), nullptr);Node* cur = _head;int sub = _head->_skipPoints.size() - 1;while (层数 >= 0) {if (大于后续结点) {cur = cur->_skipPoints[sub];}else if (小于等于后续结点) {prev[sub] = cur;//记录前序结点sub--;//向下走一层}}return prev;
}void add(int num) {vector<Node*> prevPoints = getPrev(num);//专门记录插入结点的前序指针的数组if (prevPoints[0]记录下一个结点元素与插入值相同) return;//重复添加int i = getLevel();//获取随机层数Node* cur = new Node(num, i);if (随机层数比现有要高) {_head和prevPoints都要增加至i层}for (i -= 1; i >= 0; i--) {按普通链表插入即可}
}

同时,因为跳表每个结点有多少层指针是随机的,因此需要写一个随机函数确定层数:
结点每增加一层的概率为p,同时设定最大层数值。

层数概率
1 - p
p * (1 - p)
p * p * (1 - p)

根据表格可知,p越小结点增加层数的概率越低。

随机函数可以使用C++随机数库实现:

int getLevel() {//使用随机数库static std::default_random_engine generator(std::chrono::system_clock::now().time_since_epoch().count());//随机数范围0 - 1,类型是doublestatic std::uniform_real_distribution<double> distribution(0.0, 1.0);int level = 1;//如果随机数小于_p同时没达到最大层数,层数++while (distribution(generator) <= _p && level < _maxLevel){++level;}return level;
}

(四).删除

删除元素同样要先找到每一层的前序结点。

之后删除时按照普通链表的方式删除即可。

同时如果删除的结点拥有唯一最高层,那么需要更新_head结点层数。

代码结构如下:

bool erase(int num) {//获取各层前序结点vector<Node*> prevPoints = getPrev(num);//没有该节点if (前序指针指向空 || 前序指向元素不是目标删除元素) {return false;}//获取待删除元素的结点,一层层删除for () {//...}//更新高度return true;
}

三.实现代码

元素以int为例,可以使用template变成模板类。

struct SkiplistNode {int _val;vector<SkiplistNode*> _skipPoints;SkiplistNode(int val, int n = 1):_val(val), _skipPoints(n, nullptr){}~SkiplistNode(){for (auto* p : _skipPoints) p = nullptr;}
};class Skiplist {typedef SkiplistNode Node;vector<Node*> getPrev(int target) {vector<Node*> prev(_head->_skipPoints.size(), nullptr);Node* cur = _head;int sub = _head->_skipPoints.size() - 1;while (sub >= 0) {if (cur->_skipPoints[sub] && target > cur->_skipPoints[sub]->_val) {cur = cur->_skipPoints[sub];}else if (!cur->_skipPoints[sub] || target <= cur->_skipPoints[sub]->_val) {prev[sub] = cur;sub--;}}return prev;}int getLevel() {static std::default_random_engine generator(std::chrono::system_clock::now().time_since_epoch().count());static std::uniform_real_distribution<double> distribution(0.0, 1.0);int level = 1;while (distribution(generator) <= _p && level < _maxLevel){++level;}return level;}
public:Skiplist() {srand(time(NULL));_head = new Node(-1);}bool search(int target) {Node* cur = _head;int sub = cur->_skipPoints.size() - 1;while (sub >= 0) {if (cur->_skipPoints[sub] && target > cur->_skipPoints[sub]->_val)//target大于下一个值, 继续向后{cur = cur->_skipPoints[sub];}else if (!cur->_skipPoints[sub] || target < cur->_skipPoints[sub]->_val)//target小于, 向下{sub--;}else return true;}return false;}void add(int num) {vector<Node*> prevPoints = getPrev(num);//专门记录插入结点的前序指针的数组//if (prevPoints[0]->_skipPoints[0] && prevPoints[0]->_skipPoints[0]->_val == num) return;//重复添加int i = getLevel();Node* cur = new Node(num, i);if (i > _head->_skipPoints.size()) {//随机层数比现有要高_head->_skipPoints.resize(i, nullptr);prevPoints.resize(i, _head);//让前序指针数组高出的指向_head,这样能将_head与结点相连}for (i -= 1; i >= 0; i--) {cur->_skipPoints[i] = prevPoints[i]->_skipPoints[i];prevPoints[i]->_skipPoints[i] = cur;}}bool erase(int num) {vector<Node*> prevPoints = getPrev(num);//如果前序为空(num大于所有节点值)或 前序下一个不是num(因为getPrev函数获得的是<=num)if (!prevPoints[0]->_skipPoints[0] || prevPoints[0]->_skipPoints[0]->_val != num) {return false;}//获取待删除元素的结点Node* cur = prevPoints[0]->_skipPoints[0];//一层层删除for (int i = cur->_skipPoints.size() - 1; i >= 0; i--) {prevPoints[i]->_skipPoints[i] = cur->_skipPoints[i];}delete cur;int n = _head->_skipPoints.size() - 1;while (n >= 0) {if (_head->_skipPoints[n] == nullptr) n--;else break;}_head->_skipPoints.resize(n + 1);return true;}
private:Node* _head;size_t _maxLevel = 32;double _p = 0.25;
};

信念和目标,必须永远洋溢在程序员内心——未名


如有错误,敬请斧正 


http://chatgpt.dhexx.cn/article/oJ7mWI6u.shtml

相关文章

SkipList(跳表)

SkipList(跳表) 文章目录 SkipList(跳表)参考前言跳表的原理跳表的插入和删除插入操作删除操作 跳表的时间空间复杂度分析时间复杂度空间复杂度 调表的基本操作插入数据删除数据 Go 实现小结 参考 https://juejin.cn/post/6844903955831619597#heading-2https://blog.csdn.net…

每日一博 - 如何理解跳表(SkipList)

文章目录 什么是跳跃表SkipList跳表关键字Why Skip ListCode跳表-查询跳表-删除跳表-插入 小结完整Code 什么是跳跃表SkipList 跳跃表(简称跳表)由美国计算机科学家William Pugh于1989年发明 论文&#xff1a; Skip lists: a probabilistic alternative to balanced trees 跳…

Skiplist(跳表)实现

前言&#xff1a;跳表本质是一种查找结构&#xff0c;相较于AVL树、红黑树、哈希表&#xff0c;实现起来要更加简单&#xff0c;而且效率也不差。 一.Skiplist概念 跳表本质也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树&#xff08;AVL树、红…

跳表(SkipList)设计与实现(java)

微信搜一搜「bigsai」关注这个有趣的程序员&#xff0c;一起做个朋友 新人原创公众号&#xff0c;求支持一下&#xff01;你的点赞三连肯定对我至关重要&#xff01; 文章已收录在 我的Github bigsai-algorithm 欢迎star 前言 跳表是面试常问的一种数据结构&#xff0c;它在很…

SkipList和java中ConcurrentSkipListMap的实现

文章目录 简介SkipListConcurrentSkipListMapSkipList的实现concurrent的实现 总结 SkipList和java中ConcurrentSkipListMap的实现 简介 一开始听说SkipList我是一脸懵逼的&#xff0c;啥&#xff1f;还有SkipList&#xff1f;这个是什么玩意。 后面经过我的不断搜索和学习&a…

跳表(SkipList)及ConcurrentSkipListMap源码解析

二分查找和AVL树查找 二分查找要求元素可以随机访问&#xff0c;所以决定了需要把元素存储在连续内存。这样查找确实很快&#xff0c;但是插入和删除元素的时候&#xff0c;为了保证元素的有序性&#xff0c;就需要大量的移动元素了。 如果需要的是一个能够进行二分查找&#…

Redis数据结构之——跳表skiplist

写在前面 以下内容是基于Redis 6.2.6 版本整理总结 一、跳表&#xff08;skiplist&#xff09; 如何理解跳表&#xff1f;在了解跳表之前&#xff0c;我们先从普通链表开始&#xff0c;一点点揭开跳表的神秘面纱~ 首先&#xff0c;普通单链表来说&#xff0c;即使链表是有序…

redis中ziplist,skiplist

ziplist压缩表 ziplist主要是为了节约内存&#xff0c;他将元素存储在一块连续的内存空间中&#xff0c;这样在查询数据的时候也可以利用CPU的缓存访问数据&#xff0c;加快查询的效率 相较于数组而言。我们知道,数组要求每个元素的大小都相同,如果我们要存储不同长度的字符串…

跳表-skiplist的简单实现

文章目录 1、什么是跳表-skiplist2、skiplist的效率如何保证&#xff1f;3、skiplist的实现4、skiplist跟平衡搜索树和哈希表的对比 1、什么是跳表-skiplist skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树和哈希表的价值是一样…

跳表:Skiplist原理介绍和优缺点

skiplist介绍 不要求上下相邻两层链表之间的节点个数有严格的对应关系&#xff0c;而是为每个节点随机出一个层数(level)。比如&#xff0c;一个节点随机出的层数是3&#xff0c;那么就把它链入到第1层到第3层这三层链表中。为了表达清楚&#xff0c;下图展示了如何通过一步步的…

skiplist 跳跃表详解及其编程实现

skiplist介绍 跳表(skip List)是一种随机化的数据结构&#xff0c;基于并联的链表&#xff0c;实现简单&#xff0c;插入、删除、查找的复杂度均为O(logN)。跳表的具体定义&#xff0c; 请参考参考维基百科 点我&#xff0c;中文版。跳表是由William Pugh发明的&#xff0c;这…

浅析SkipList跳跃表原理及代码实现

转载请注明&#xff1a;http://blog.csdn.net/ict2014/article/details/17394259 SkipList在leveldb以及lucence中都广为使用&#xff0c;是比较高效的数据结构。由于它的代码以及原理实现的简单性&#xff0c;更为人们所接受。我们首先看看SkipList的定义&#xff0c;为什么叫…

跳表(skiplist)的理解

听到跳表(skiplist)这个名字,既然是list,那么应该跟链表有关。 跳表是有序链表,但是我们知道,即使对于排过序的链表,我们对于查找还是需要进行通过链表的指针进行遍历的,时间复杂度很高依然是O(n),这个显然是不能接受的。是否可以像数组那样,通过二分法进行查找呢,但…

SkipList详解

本文参考&#xff1a;《大数据日知录》 概念 SkipList是一种用来代替平衡树的数据结构。 虽然在最坏的情况下SkipList的效率要低于平衡树&#xff0c;但是大多数情况下效率仍然非常高&#xff0c;其插入、删除、查找的时间复杂度都是O(log(N))。 除了高效外&#xff0c;其实现…

跳表(skipList)

一、为何有skipList这种数据结构的出现 我们知道二分查找算法之所以能达到 O(logn) 这样高效的一个重要原因在于它所依赖的数据结构是数组&#xff0c;数组支持随机访问一个元素&#xff0c;通过下标很容易定位到中间元素。而链表是不支持随机访问的&#xff0c;只能从头到尾依…

跳表 skiplist 简介

跳表 skiplist 跳表 (Skip List) 是由 William Pugh 在 1990 年发表的文章 Skip Lists: A Probabilistic Alternative toBalanced Trees 中描述的一种查找数据结构&#xff0c;支持对数据的快速查找&#xff0c;插入和删除。 对于 AVL 树、红黑树等平衡树&#xff0c;在插入过…

SkipList(跳跃表)详解

Introduction: skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff08;Searching&#xff09;&#xff0c;即根据给定的key&#xff0c;快速查到它所在的位置&#xff08;或者对应的value&#xff09; 一般用于解决查找问题的数据结构分为两个大类…

docker中国镜像

一,如果是像redis,mysql等官方的镜像,直接配置阿里云的镜像就可以 1,注册阿里云账号,并登录 2,打开这个网址 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 3,页面上会教怎么设置,如下面的截图 二,如果是私有仓库的镜像 非常感谢原作者:https://www.jiansh…

Docker 使用国内镜像仓库

Docker 使用国内镜像仓库 1、问题描述2、总结 1、问题描述 由于某些原因&#xff0c;导致Docker镜像在国内下载速度特别慢。所以为了沉浸式开发。最好切换为国内源。这里以163 的镜像仓库举例。首先修改/etc/docker/daemon.json配置文件。 sudo vi /etc/docker/daemon.json…

MacOS上配置docker国内镜像仓库地址

背景 docker官方镜像仓库网速较差&#xff0c;我们需要设置国内镜像服务 我的MacOS docker版本如下 设置docker国内镜像仓库地址 点击Settings点击Docker Engine修改配置文件&#xff0c;添加registry-mirrors {"builder": {"gc": {"defaultKeepS…