SkipList ----- 跳表
这里写目录标题
- 什么是跳表
- 跳表的性能分析
- 跳表的实现
什么是跳表
1.跳表,也是基于链表实现的,他其实和链表一样,也是一个数据结构中的查找结构,用于解决一些查找问题而产生。
2.跳表虽然是基于链表所实现的,但是不同于链表的是,他的查询效率比较高,也可以说他的出现是链表的一个优化,具体优化的思想如下:
- 链表我们知道,每次都是一个节点指向一个节点,中间没有跳过任何节点,一条路指到头的,所以他的查找的时间复杂度都是O(N)。如下图,就是一个链表的形式。
- 因为链表的查找时间很慢,所以制造跳表的人就想了想,如果说每次相邻的两个节点升高一层,增加一个指针,让指针指向下下个节点,这样新增加的节点又构成了一个链表,并且长度是原来链表长度的一般,这样我们查找一个数的时候,查找效率会提高一倍。如下图,就是优化后的链表:
- 这时他又想提高效率,在每隔两个双层的节点上再增加一层,这样提高的效率就很高了,如下图:
- 跳表其实就是这样被创造出来的,按照上面的情况分析,每一层都是下一层节点数的一半,这样的查找会根据节点所处层的高低将查找的时间复杂度降低为O(log n),但是虽然这样查找的效率很高,但是对于跳表的操作就会显得很复杂,如果插入或者删除一个节点的话,那么就会打乱每两个节点升高一层这样的结构,所以这样的结构还是出现问题了,所以也有了下一步的优化。
- 跳表的设计为了避免这样的问题出现,做了一个大胆的处理,不严格要求对应的比例关系,直接每次插入节点的时候随机一个高度插入,这样插入和删除某个节点的时候,就不用考虑周围节点的高度变化了。
跳表的性能分析
因为跳表在插入节点的时候,是随机一个节点的高度进行插入的,所以会指定一个最高的高度,并且会设计一个概率函数去制定每次插入的节点的高度是多少,并且计算高度的这段伪函数如下:
在Redis中跳表的实现中两个值的参数分别为:
p = 1/4; //概率
maxLevel = 32; //最高高度
通过上面的伪代码,我们可以看出来,高度越高的节点,出现的概率越小,因为random()这个区间的分布为0~1,而p = 1/4,只有当random()<p的时候,才能让高度增加,而每次都将是1/4的n次方(n代表的是出现到几),概率是非常小的。
所以也有了下面这个概率的公式,为取到固定高度的公式:
跳表的实现
其中删除节点和插入节点的时候比较复杂.
首先,我们先看操作的节点信息:
template<class T>
struct SkipNode
{T data;vector<SkipNode*> _vNext;SkipNode(int val, int level):_vNext(level, nullptr), data(val){}
};
1.因为是跳表操作,所以每次增删的时候,都必须知道增加节点的前一个节点和后一个节点,此时要用数组保存起来,为了方便后面进行修改:
//寻找插入或删除节点时要修改的节点vector<Node*> _Findpre(const T& val){vector<Node*> ret(_head->_vNext.size(),_head); //提前开好空间,然后赋值Node* p= _head;int level = _head->_vNext.size() - 1;while (level >= 0){if (p->_vNext[level] == nullptr || p->_vNext[level]->data >= val) //往下跳{ret[level] = p; //保存插入节点直接相连的前面个节点level--; //这块注意一定要赋值,不能给push_back,因为push_back后的结果} //会让原来水平的方向颠倒,因为我这个是level是--。else{p = p->_vNext[level]; //往后跳}}return ret;}
2.插入节点:插入节点是有些没法,但是只要在插入节点的找到该节点插入的位置,并且插入节点的高度,并且将需要修改的节点保存起来,然后进行按层插入即可,如下:
/插入节点bool Insert(T val){if (_head == nullptr) //如果调表为空,直接插入{Node* p = new Node(val, 1);_head = p;return true;}size_t h = Gethight();Node* node = new Node(val, h);vector<Node*> pre = _Findpre(val); //寻找插入节点时需要修改的值if (h > _head->_vNext.size()) //如果新产生的节点高度大于根节点高度{_head->_vNext.resize(h,nullptr); //修改根节点高度,那么赋值应该还是根节点的datapre.resize(h,_head);}for (size_t i = 0; i < h; ++i) //插入节点{node->_vNext[i] = pre[i]->_vNext[i];pre[i]->_vNext[i] = node;}return true;}
3.删除操作也一样,只需要找到删除节点的前一个节点,就可以进行操作了:
//删除节点bool erase(T val){if (_head == nullptr || FindVal(val) == false) //节点不存在表中{return false;}//找到节点删除vector<Node*> pre = _Findpre(val);Node* del = pre[0]->_vNext[0];for (int i = 0; i < del->_vNext.size(); ++i){pre[i]->_vNext[i] = del->_vNext[i];}delete del;//如果说删除的节点是存在的最高节点,那么头节点的高度要降int i = _head->_vNext.size()-1;while (i > 0){if (_head->_vNext[i] == nullptr){--i;}else{break;}}_head->_vNext.resize(i+1);return true;}
4.整体代码如下:
#include<iostream>
#include<vector>
using namespace std;
template<class T>
struct SkipNode
{T data;vector<SkipNode*> _vNext;SkipNode(int val, int level):_vNext(level, nullptr), data(val){}
};template<class T>
class SkipTable
{typedef struct SkipNode<T> Node;
public:SkipTable():_head(nullptr),_maxLevel(32), _p(0.25){}
public://获取该新节点的高度size_t Gethight(){size_t level = 1;// rand() ->[0, RAND_MAX]之间 //经典取随机数算法while (rand() <= RAND_MAX * _p && level < _maxLevel) //获取多个随机数,手机随机数的大小小于{ //_maxLevel的数量++level;}return level;}//查找节点是否存在跳表中bool FindVal(const T& val) {if (_head == nullptr){return false;}Node* p = _head;int level = p->_vNext.size()-1; //头节点的高度(也代表者跳表中存在节点的最高高度)while (level >= 0) {if (p->_vNext[level] == nullptr || p->_vNext[level]->data > val) //往下跳{level--;}else if (p->_vNext[level]->data == val){return true;}else{p = p->_vNext[level]; //往后跳}}return false;}//寻找插入或删除节点时要修改的节点vector<Node*> _Findpre(const T& val){vector<Node*> ret(_head->_vNext.size(),_head); //提前开好空间,然后赋值Node* p= _head;int level = _head->_vNext.size() - 1;while (level >= 0){if (p->_vNext[level] == nullptr || p->_vNext[level]->data >= val) //往下跳{ret[level] = p; //保存插入节点直接相连的前面个节点level--; //这块注意一定要赋值,不能给push_back,因为push_back后的结果} //会让原来水平的方向颠倒,因为我这个是level是--。else{p = p->_vNext[level]; //往后跳}}return ret;}//插入节点bool Insert(T val){if (_head == nullptr) //如果调表为空,直接插入{Node* p = new Node(val, 1);_head = p;return true;}size_t h = Gethight();Node* node = new Node(val, h);vector<Node*> pre = _Findpre(val);if (h > _head->_vNext.size()) //如果新产生的节点高度大于根节点高度{_head->_vNext.resize(h,nullptr); //修改根节点高度,那么赋值应该还是根节点的datapre.resize(h,_head);}for (size_t i = 0; i < h; ++i) //插入节点{node->_vNext[i] = pre[i]->_vNext[i];pre[i]->_vNext[i] = node;}return true;}//删除节点bool erase(T val){if (_head == nullptr || FindVal(val) == false) //节点不存在表中{return false;}//找到节点删除vector<Node*> pre = _Findpre(val);Node* del = pre[0]->_vNext[0];for (int i = 0; i < del->_vNext.size(); ++i){pre[i]->_vNext[i] = del->_vNext[i];}delete del;//如果说删除的节点是存在的最高节点,那么头节点的高度要降int i = _head->_vNext.size()-1;while (i > 0){if (_head->_vNext[i] == nullptr){--i;}else{break;}}_head->_vNext.resize(i+1);return true;}void Print(){for (int i = _head->_vNext.size()-1; i >= 0; --i){Node* p = _head;while (p){cout << p->data << "->";p = p->_vNext[i];}cout << endl;}}
private:Node* _head;size_t _maxLevel;float _p;
};
SkipList ----- 跳表相关推荐
- skiplist 跳表(1)
最近学习中遇到一种新的数据结构,很实用,搬过来学习. 原文地址:skiplist 跳表 为什么选择跳表 目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等. ...
- skiplist 跳表(2)-----细心学习
快速了解skiplist请看:skiplist 跳表(1) http://blog.sina.com.cn/s/blog_693f08470101n2lv.html 本周我要介绍的数据结构,是我非常非 ...
- SkipList(跳表)
SkipList(跳表) 文章目录 SkipList(跳表) 参考 前言 跳表的原理 跳表的插入和删除 插入操作 删除操作 跳表的时间空间复杂度分析 时间复杂度 空间复杂度 调表的基本操作 插入数据 ...
- Redis数据结构-SkipList(跳表)
Redis数据结构-SkipList(跳表) SkipList(跳表)首先是链表,但与传统链表相比有几点差异: 元素按照升序排列存储 节点可能包含多个指针,指针跨度不同. 查找19时 可见效率会比较高 ...
- Java版skiplist跳表详解
skiplist简介 skiplist 是 一个概率型数据结构,查找.删除.插入的时间复杂度都是O(logN). skiplist是由多层有序的链表组成的,来加快查找速度. 其中第0层包含了所有元素, ...
- redis为什么要使用skiplist跳表
1.什么是skiplist跳表 跳表是一种特殊的链表,特殊的点在于其可以进行二分查找.普通的链表要查找元素只能挨个遍历链表中的所有元素,而跳表则利用了空间换时间的策略,在原来有序链表的基础上面增加了多 ...
- skiplist跳表的 实现
文章目录 前言 跳表结构 时间复杂度 空间复杂度 高效的动态插入和删除 跳表索引的动态更新 总结 详细实现 前言 rocksdb 的memtable中默认使用跳表数据结构对有序数据进行的管理,为什么呢 ...
- SkipList 跳表
转载:https://blog.csdn.net/fw0124/article/details/42780679 为什么选择跳表 说起跳表,我们还是要从二分查找开始. 二分查找的关键要求有两个, 1 ...
- Skiplist跳表详解及其模拟实现
文章目录 跳表 1.跳表的概念 2.Skiplist在插入时采用随机层数的方法是如何保证效率的呢? 3.跳表的模拟实现 4.跳表VS平衡搜索树和哈希表 跳表 1.跳表的概念 跳表是基于有序链表扩展实 ...
- 跳跃表 skipList 跳表的原理以及golang实现
跳跃表 skipList 调表的原理以及golang实现 调表skiplist 是一个特殊的链表,相比一般的链表有更高的查找效率,跳跃表的查找,插入,删除的时间复杂度O(logN) Redis中的有序 ...
最新文章
- 关于vs生成app错误提示,提醒Execution failed for task ':transformClassesWithDexForDebug'.
- 北京林大计算机科技应为abc哪类,北京林业大学新生入学要准备什么?
- HTML+MYSQL+PHP搭建带有cookie的登陆页面
- 赫塔•米勒获诺贝尔文学奖说明了什么?
- 日期居然用字符串保存?我笑了
- ajax正确返回数据,却进入了error分支
- Spring-web-HandlerMethodReturnValueHandler
- 5.VMware View 5.0安装与部署-安装view agent与模版
- laravel mysql rand_Laravel-雄辩或流利的随机行
- jedate日期插件使用
- MS DTC服务无法启动解决方法
- postman安装html插件安装,Postman 安装与更新
- 【RNAseq】差异分析
- 张凯龙 西北工业大学计算机学院,张凯龙的个人主页-西北工业大学教师个人主页...
- 《RAFT-Stereo:Multilevel Recurrent Field Transforms for Stereo Matching》论文笔记
- 关于高精度地图定义的探讨
- nginx多域名重定向到不同的二级域名
- 用c打印26个小写字母
- java 环境变量的设置
- 手机里同时放电信卡和联通卡诡异情况描述