【数据结构】跳表Skiplist

article/2025/8/7 21:49:30

文章目录

  • 跳表--skiplist
    • skiplist的概念
    • skilplist的原理
    • skilplist的实现
      • 随机值函数
      • 跳表节点
    • 跳表框架
      • 查找函数
      • 寻找前置节点
      • 添加元素
      • 删除元素
      • 打印链表
    • 测试结果
    • Skiplist与其他Key-Value结构的比较

跳表–skiplist

skiplist的概念

skiplist本质上也是一种查找结构,用于解决算法中的查找问题。skiplist,顾名思义,首先它是一个list。它是在有序链表的基础上发展起来的。有序链表的的查找数据的时间复杂度是O(n);

skilplist相对于list做出了优化,类似于二分查找,查找的时间复杂度是log(n);

  • 假如我们每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点。这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半。由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。
  • 以此类推,在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一
    个指针,从而产生第三层链表。
  • 这样查找过程就非常类似二分查找,使得查找的时间复杂度可以降低到O(log n)。但是在插入或者删除一个节点时,就打乱了上下的层次关系。**为了维持2:1的对应关系,需要调整整个链表,时间复杂度就又变成了O(n)。 **

image-20221210230152671

在设计跳表时,设计师对Skiplist作出了大胆的优化。不再严格要求对于的比例关系,而是插入一个节点时,随机赋予一个层数。当然这里的层数是在一个范围内的随机值

image-20221210230214786

skilplist的原理

层数的随机值

  • 最大层数maxLevel
  • p:增加一层的概率

image-20221210235753818

在Redis中的skiplist,maxlevel取值为32,p为1/4

数学原理

P表示新增加一层的概率,节点层数至少为1。而大于1的节点层数,满足一个概率分布。

  • 节点层数恰好等于1的概率为1-p。
  • 节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。
  • 节点层数大于等于3的概率为p2,而节点层数恰好等于3的概率为p2*(1-p) 。
  • 节点层数大于等于4的概率为p3,而节点层数恰好等于4的概率为p3*(1-p) 。

通过错位相减,可以得到平均的层数:

image-20221211000452257

skilplist的实现

跳表OJ:

https://leetcode.cn/problems/design-skiplist/

随机值函数

因为插入一个节点时,层数是随机的,所以需要设计一个随机值函数。

C++的写法

int RandomLevel()
{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);size_t level = 1;// _p是增加一层的概率,_maxLevel是最大的层数while (distribution(generator) <= _p && level < _maxLevel){++level;}return level;
}

C语言的写法

int RandomLevel()
{size_t level = 1;// RAND_MAX是一个宏,即最大的随机值while (rand() < RADN_MAX *_p && level < _maxLevel){level++;}return level;
}

跳表节点

template<class K,class V>
struct skiplistnode{//存储的键值对K _key;V _value;vector<skiplistnode*> _nextV;//创建一个节点skiplistnode(K key,V value,int level):_key(key),_value(value),_nextV(level,nullptr){}
};

跳表框架

跳表的主要功能有增加元素,删除元素,操作元素等。跳表是一个Key-Value的查找结构,所以需要实现一个模板。

template <class K, class V>
class skiplist
{typedef skiplistnode<K, V> Node;
public:// 构造函数skiplist(){srand(time(0));_head = new Node(K(), V(), 1);}// 查找函数pair<V, bool> search(K target){}//查找前置结点,返回一个vector,包含target每一层的前置节点vector<Node *> Findprev(K target){}//添加元素函数void add(K key, V value){}//删除函数bool erase(K target){}//打印链表,每一层为一个链表void print(){}//随机值生成函数int RandomLevel(){}
private:double _p = 0.25;int _maxLevel = 32;Node *_head;
};

查找函数

查找关键字Key,返回对应的值。

// 查找函数
pair<V, bool> search(K target)
{Node *cur = _head;int level = _head->_nextV.size() - 1;while (level >= 0){if (cur->_nextV[level] && target > cur->_nextV[level]->_key){cur = cur->_nextV[level];}else if (cur->_nextV[level] == nullptr || target < cur->_nextV[level]->_key){level--;}else{return make_pair(cur->_nextV[level]->_value, true);}}return make_pair(V(), false);
}

寻找前置节点

查找前置结点,返回一个vector,包含关键字target每一层的前置节点指针。

vector<Node *> Findprev(K target)
{int level = _head->_nextV.size() - 1;vector<Node *> prevnode(level + 1, _head);// 寻找前置节点Node *cur = _head;while (level >= 0){if (cur->_nextV[level] && target > cur->_nextV[level]->_key){cur = cur->_nextV[level];}else if (cur->_nextV[level] == nullptr || target <= cur->_nextV[level]->_key){prevnode[level] = cur;level--;}}return prevnode;
}

添加元素

向链表这插入一个键值对

void add(K key, V value)
{// 先获取插入点的层数int newlevel = RandomLevel();Node *newnode = new Node(key, value, newlevel);// 创建一个数组用于获取每一层的前置结点vector<Node *> prev = Findprev(key);// 如果层数比哨兵位节点还要大,则需要调整大小哨兵位节点的层数if (newlevel > _head->_nextV.size()){_head->_nextV.resize(newlevel, nullptr);prev.resize(newlevel, _head);}// 连接结点for (size_t i = 0; i < newlevel; i++){newnode->_nextV[i] = prev[i]->_nextV[i];prev[i]->_nextV[i] = newnode;}
}

删除元素

输入一个关键字Key,删除链表中第一个Key元素

bool erase(K target)
{vector<Node *> prev = Findprev(target);// 判断是否有当前的值,如果没有返回falseif (prev[0]->_nextV[0] == nullptr || prev[0]->_nextV[0]->_value != target){return false;}else{// 删除结点Node *del = prev[0]->_nextV[0];int level = del->_nextV.size();for (size_t i = 0; i < level; i++){prev[i]->_nextV[i] = del->_nextV[i];}delete del;// 判断是否要调整哨兵位的层数int n = _head->_nextV.size() - 1;while (n >= 0){if (_head->_nextV[n]){break;}else{n--;}}_head->_nextV.resize(n + 1);}return true;
}

打印链表

打印链表,每一层为一个链表。

void print()
{int level = _head->_nextV.size() - 1;while (level--){Node *cur = _head;while (cur){if (cur->_nextV[level] != nullptr){cout << cur->_nextV[level]->_value << " ";}cur = cur->_nextV[level];}cout << endl;}
}

测试结果

#include"Skilplist.hpp"
#include<iostream>int main(){skiplist<int,int> sklist;sklist.add(3,3);sklist.add(6,6);sklist.add(9,9);sklist.add(11,11);sklist.add(7,7);sklist.add(5,5);sklist.add(11,11);sklist.add(13,13);sklist.add(1,1);sklist.add(2,2);sklist.add(15,15);cout<<"del before.........."<<endl;sklist.print();sklist.erase(7);sklist.erase(8);sklist.erase(11);sklist.erase(13);cout<<"del after..........."<<endl;sklist.print();auto it1=sklist.search(9);cout<<"search 9:  "<<it1.first<<"; bool: "<<it1.second<<endl;auto it2=sklist.search(13);cout<<"search 13:  "<<it2.first<<"; bool: "<<it2.second<<endl;return 0;
}

image-20221212113307323

Skiplist与其他Key-Value结构的比较

  1. skiplist相比平衡搜索树(AVL树和红黑树)对比,都可以做到遍历数据有序,时间复杂度相差不大。skiplist的优势是:
  • skiplist实现更简单,容易控制。平衡树增删查改遍历都更复杂。
  • skiplist的额外空间消耗更低。平衡树节点存储每个值有三叉链,平衡因子/颜色等消耗。
  1. skiplist相比哈希表而言 ,优势不大。
  • 哈希表平均时间复杂度是O(1),而跳表的时间复杂度是O(log n)
  • 哈希表空间复杂度更高
  • 相当于哈希表,跳表遍历数据有序
  • 哈希表扩容有性能损耗
  • 哈希表再极端场景下哈希冲突高,效率下降厉害,需要挂载红黑树

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

相关文章

Skiplist跳表详解及其模拟实现

文章目录 跳表1.跳表的概念2.Skiplist在插入时采用随机层数的方法是如何保证效率的呢?3.跳表的模拟实现4.跳表VS平衡搜索树和哈希表 跳表 1.跳表的概念 跳表是基于有序链表扩展实现的。对于一个普通的有序链表&#xff0c;我们查找数据的时间复杂度是O(N)。而跳表的出现&…

skiplist - 跳表

一 前言 跳表(skiplist、跳跃表) 是一个很优秀的数据结构&#xff0c;比如用于 Redis、levelDB等出名的开源项目上。跳表在原有的有序链表上面增加了多级索引&#xff0c;通过索引来实现快速查找。 它的结构特点在名称能很好的体现出来&#xff0c;就像兔子一样&#xff0c;蹦…

Leveldb skiplist 实现及解析

skiplist 原理介绍 skiplist 由William Pugh 在论文Skip Lists: A Probabilistic Alternative to Balanced Trees 中提出的一种数据结构,skiplist 是一种随机化存储的多层线性链表结构,插入,查找,删除的都是对数级别的时间复杂度。skiplist 和平衡树有相同的时间复杂度,但…

跳表SkipList介绍与实现

目录 一.跳表介绍 二.实现思路 &#xff08;一&#xff09;.结点结构 &#xff08;二&#xff09;.检索 &#xff08;三&#xff09;.插入 &#xff08;四&#xff09;.删除 三.实现代码 一.跳表介绍 跳表是一种随机化数据结构&#xff0c;主要用于快速检索数据。实质上…

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;在插入过…