目录

一.跳表介绍

二.实现思路

(一).结点结构

(二).检索

(三).插入

(四).删除

三.实现代码


一.跳表介绍

跳表是一种随机化数据结构,主要用于快速检索数据。实质上是一种可以进行二分查找的有序链表。时间复杂度可以达到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;
};

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


如有错误,敬请斧正

跳表SkipList介绍与实现相关推荐

  1. java 跳表_跳表 skiplist

    最初知道跳表(Skip List)是在看redis原理的时候,redis中的有序集合使用了跳表作为数据结构.接着就查了一些资料,来学习一下跳表.后面会使用java代码来实现跳表. 跳表简介 跳表由Wi ...

  2. java数据结构红黑树上旋下旋_存储系统的基本数据结构之一: 跳表 (SkipList)

    在接下来的系列文章中,我们将介绍一系列应用于存储以及IO子系统的数据结构.这些数据结构相互关联又有着巨大的区别,希望我们能够不辱使命的将他们分门别类的介绍清楚.本文为第一节,介绍一个简单而又有用的数据 ...

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

    文章目录 什么是跳跃表SkipList 跳表关键字 Why Skip List Code 跳表-查询 跳表-删除 跳表-插入 小结 完整Code 什么是跳跃表SkipList 跳跃表(简称跳表)由美国 ...

  4. 跳表-skiplist的简单实现

    文章目录 1.什么是跳表-skiplist 2.skiplist的效率如何保证? 3.skiplist的实现 4.skiplist跟平衡搜索树和哈希表的对比 1.什么是跳表-skiplist skip ...

  5. 为啥 redis 使用 跳表 (skiplist) 而不是使用 red-black?

    基本结论 1.实现简单. 2.区间查找快.跳表可以做到O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了. 3.并发环境优势.红黑树在插入和删除的时候可能需要做一些reb ...

  6. 什么是跳表 skiplist ?

    什么是跳表 skiplist ? 文章目录 什么是跳表 skiplist ? 特性 实现 结构 查找 插入 删除 完整代码 参考 跳表可以快速地查找.插入.删除.据说可以替代红黑树.Redis中的有序 ...

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

    skiplist介绍 不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level).比如,一个节点随机出的层数是3,那么就把它链入到第1层到第3层这三层链表中.为了 ...

  8. 跳表SkipList

    1.聊一聊跳表作者的其人其事 2. 言归正传,跳表简介 3. 跳表数据存储模型 4. 跳表的代码实现分析 5. 论文,代码下载及参考资料 <1>. 聊一聊作者的其人其事  跳表是由Will ...

  9. 数据映射--跳表(skiplist)

    http://blog.sina.com.cn/s/blog_693f08470101n2lv.html 本周我要介绍的数据结构,是我非常非常喜欢的一个数据结构,因为咱也是吃过平衡二叉树的苦的人啊T_ ...

最新文章

  1. PowerDesigner与Rose详解教程
  2. python3.6手册中文版-Python3.6.5标准库 参考文档 完整pdf中文版
  3. php 逗号运算符,基础篇PHP运算符总结宝典
  4. Batch Norm常用方法
  5. 【玩法设计】炉石传说吃鸡竞技场构思
  6. leetcode 219. 存在重复元素 II(规定步长)
  7. 图灵机器人调用数据恢复_机器人也能撩妹?python程序员自制微信机器人,替他俘获女神芳心...
  8. 2003 r2 64 iis php mysql_关于在win2003中,iis+php+mysql 配置的问题
  9. 近期新机发布一览:最便宜的只需699元!
  10. 物联网金融:下一个风口?
  11. servlet监听器Listener(理论+例子)
  12. LED显示驱动(七):图层基本测试总结
  13. Sharepoint学习笔记---SPList--External List因BCS的Throttling limit 节流限制导致的错误
  14. dicom文件的后缀_DCM文件扩展名 - 什么是.dcm以及如何打开? - ReviverSoft
  15. 模糊聚类 matlab 代码,模糊聚类+Matlab代码
  16. U盘/移动硬盘 有写保护怎么解除【未解决】
  17. 恒星物联-河道液位监测系统方案 液位监测
  18. kafka内外网连接问题
  19. 最新华农c语言教材答案,华农C语言题目及答案完整版
  20. 【Paper】2017_Consensus of linear multi-agent systems with exogenous disturbance generated from hetero

热门文章

  1. 一个通过Rmd文件输出表格到word的简单方法
  2. Flask学习笔记(四): Flask与数据库连接
  3. 非编系统工作站需要什么样的配置?(二)
  4. Seagull island
  5. TESRA旗下InFlex计算平台使用流程
  6. GPIO 配置之ODR, BSRR, BRR 详解
  7. root用户被提示:Operation not permitted
  8. Android与HEIF格式图片适配方法
  9. 稳压二极管、肖特基二极管、ESD静电保护二极管、TVS瞬态抑制保护管
  10. RS232电平 RS485电平 RS422电平