跳跃表

跳跃列表(也称跳表)是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作须要O(logn)平均时间)。



基本上。跳跃列表是对有序的链表添加上附加的前进链接,添加是以随机化的方式进行的。所以在列表中的查找能够高速的跳过部分列表元素,因此得名。全部操作都以对数随机化的时间进行。

例如以下图所看到的。是一个即为简单的跳跃表。

传统意义的单链表是一个线性结构。向有序的链表中插入一个节点须要O(n)的时间。查找操作须要O(n)的时间。假设我们使用图中所看到的的跳跃表。就能够大大降低降低查找所需时间。

由于我们能够先通过每一个节点的最上层的指针先进行查找,这样子就能跳过大部分的节点。然后再缩减范围,对以下一层的指针进行查找,若仍未找到,缩小范围继续查找。

上面基本上就是跳跃表的思想。每个结点不单单仅仅包括指向下一个结点的指针。可能包括非常多个指向兴许结点的指针,这样就能够跳过一些不必要的结点,从而加快查找、删除等操作。对于一个链表内每一个结点包括多少个指向兴许元素的指针,这个过程是通过一个随机函数生成器得到。这样子就构成了一个跳跃表。

构造

由图不难理解跳跃表的原理,能够看出。跳跃表中的一个节点是有不同数目的后继指针的。

那么问题来了,这详细是怎样实现的?这些节点是怎样构造的

分析

我们不可能为每一种后继指针数目的节点分配一种大小类型的节点,那我们就提取共性,看这些节点有何共通之处。

这些节点可看做由两部分构成:数据域、指针域。

数据域包含key-Value,指针域是后继指针的集合。

那怎样在节点中保存后继指针集合呢?用一个二级指针,分配节点的时候指向动态分配的后继指针数组。这个方法似乎可行,但问题在于我们的节点也是动态分配的,这种话,在释放节点的时候还须要先释放节点中动态分配的数组。

释放操作比較繁琐。

灵光一闪!之前本博客中介绍了一种称为“零数组”的技术,或许能够帮到我们。(详情点击

零数组是gcc的扩展特性。只是在C99中,能够用类似的声明来实现。

struct Node{
KeyType key;
ValueType value;
struct Node* forward[0]; //C99这样玩:struct Node* forward[]
};

动态分配节点能够这样写:

(struct Node *)malloc(sizeof(struct Node) + length*sizeof(struct Node*)); //length是后继指针数组的长度

这种话。我们能够像訪问数组那样訪问forward。且释放的时候仅仅释放动态分配的节点就可以

(forward仅仅是起一个标记的作用)



当然,另一种更通用的技巧,和零数组的思想类似

struct Node{
KeyType key;
ValueType value;
struct Node* forward[1];
};

我们在这里用符合不论什么C标准的定义,定义一个1个元素的数组,用来占位。然后动态分配一大块空间。我们通过对这个1元素数组的越界訪问,訪问到其后分配的空间



我们能够定义一个函数专门负责分配不同大小的节点:

void NewNodeWithLevel(const int& level, struct Node& node){
//新结点空间大小
int total_size = sizeof(struct Node) + level*sizeof(struct Node*);
//申请空间
node = (struct Node)malloc(total_size);
assert(node != NULL);
}

查找

我们以查找19为例。图解查找过程。

先从最上层的跳跃区间大的层開始查找。从头结点開始。首先和23进行比較,小于23,(此时查找指针在图中“1”位置处)。查找指针到下一层继续查找。

然后和9进行推断,大于9,查找指针再往前走一步和23比較,小于23。(此时查找指针在图中“2”位置处) 此时这个值肯定在9结点和23结点之间。查找指针到下一层继续查找。

然后和13进行推断。大于13,查找指针再往前走一步和23比較,小于23,(此时查找指针在图中“3”位置处)此时这个值肯定在13结点和23结点之间。查找指针到下一层继续查找。

此时,我们和19进行推断。找到了。

好了,看完这个样例,你一定对跳转表的查找操作有了清晰的理解。至于代码实现也不难了。

插入

插入和删除的实现很像对应的链表操作。除了"高层"元素必须在多个链表中插入或删除之外。

插入包括例如以下几个操作:1、查找到须要插入的位置 2、申请新的结点 3、调整指针。

由于找到插入点之后,新生成节点,新节点按概率出如今每层上,故须要保存全部层的后继指针。我们用一个暂时数组保存全部层的插入点处的后继指针。

在寻找插入点的时候就能够完毕赋值。

for(i = list->level; i >= 0; --i){
while(x->forward[i]->key < key){
x = x->forward[i];
}
update[i] = x;
}

删除

删除操作类似于插入操作,包括例如以下3步:1、查找到须要删除的结点 2、删除结点  3、调整指针

相同。须要一个暂时数组保存每层的指针域。原理和插入类似。不再赘述。

【关于释放跳转表】

释放表的操作比較简单,仅仅要像单链表一样释放表就可以。

【跳跃表使用概率均衡技术而不是使用强制性均衡,因此,对于插入和删除结点比传统上的平衡树算法更为简洁高效。

分析

跳跃列表是按层建造的。底层是一个普通的有序链表。每一个更高层都充当以下列表的“高速跑道”,这里在层 i 中的元素按某个固定的概率 p (通常为0.5或0.25)出如今层 i+1 中。平均起来。每一个元素都在 1/(1-p) 个列表中出现,而最高层的元素(一般是在跳跃列表前端的一个特殊的头元素)在 O(log1/p n) 个列表中出现。

要查找一个目标元素。起步于头元素和顶层列表,并沿着每一个链表搜索。直到到达小于或着等于目标的最后一个元素。

在每一个链表中预期的查找步数显而易见是 1/p。所以查找的整体代价是 O((log1/p n) / p),当p 是常数时是 O(log n)。通过选择不同 p 值。就能够在查找代价和存储代价之间作出权衡。



跳跃列表不像某些传统平衡树数据结构那样提供绝对的最坏情况性能保证,由于用来建造跳跃列表的扔硬币方法总有可能(虽然概率非常小)生成一个糟糕的不平衡结构。可是在实际中它工作的非常好。随机化平衡方案比在平衡二叉查找树中用的确定性平衡方案easy实现。跳跃列表在并行计算中也非常实用,这里的插入能够在跳跃列表不同的部分并行的进行,而不用全局的数据结构又一次平衡。



性能】

空间复杂度:O(n)

查找、插入和删除操作的时间复杂度都为: O(logn)

随机跳跃表表现性能也非常不错,节省了大量复杂的调节平衡树的代码。其效率与红黑树、伸展树等这些平衡树能够说相差不大。

但跳跃表还在并发环境下有优势。在并发环境下。假设要更新数据,跳跃表须要更新的部分就比較少,锁的东西也就比較少。所以不同线程争锁的代价就相对少了,而红黑树有个平衡的过程,牵涉到大量的节点,争锁的代价也就相对较高了。

性能也就不如前者了。

应用

了解过Redis的都知道,Redis有一个非常实用的数据结构:SortSet,基于它,我们能够非常轻松的实现一个Top N的应用。

这个SortSet底层就是利用跳表实现的。

跳表也被用在leveldb中。在一些词典结构中中也经经常使用跳表来实现字典,加快查找速度。

总结

作为一种简单的数据结构,在大多数应用中Skip lists可以取代平衡树。Skiplists算法很easy实现、扩展和改动。

Skip lists和进行过优化的平衡树有着相同高的性能,Skip lists的性能远远超过未经优化的平衡二叉树。



引用发明者William Pugh的话:

“跳跃列表是在非常多应用中有可能替代平衡树而作为实现方法的一种数据结构。跳跃列表的算法有同平衡树一样的渐进的预期时间边界,而且更简单、更高速和使用更少的空间。”



【參考】

关于代码參考这里:http://blog.csdn.net/ict2014/article/details/17394259

----------------------------------
感谢您的訪问。希望对您有所帮助。 欢迎大家关注、收藏以及评论。
----------------------------------

查找——图文翔解SkipList(跳跃表)的更多相关文章

  1. 查找——图文翔解HashTree(哈希树)

    引 在各种数据结构(线性表.树等)中,记录在结构中的相对位置是随机的.因此在机构中查找记录的时须要进行一系列和keyword的比較.这一类的查找方法建立在"比較"的基础上.查找的效 ...

  2. 查找——图文翔解Treap(树堆)

    之前我们讲到二叉搜索树,从二叉搜索树到2-3树到红黑树到B-树. 二叉搜索树的主要问题就是其结构与数据相关,树的深度可能会非常大,Treap树就是一种解决二叉搜索树可能深度过大的还有一种数据结构. T ...

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

    本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈“跳跃表”的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代 ...

  4. redis skiplist (跳跃表)

    redis skiplist (跳跃表) 概述 redis skiplist 是有序的, 按照分值大小排序 节点中存储多个指向其他节点的指针 结构 zskiplist 结构 // 跳跃表 typede ...

  5. 小白也能看懂的Redis教学基础篇——朋友面试被Skiplist跳跃表拦住了

    各位看官大大们,双节快乐 !!! 这是本系列博客的第二篇,主要讲的是Redis基础数据结构中ZSet(有序集合)底层实现之一的Skiplist跳跃表. 不知道那些是Redis基础数据结构的看官们,可以 ...

  6. 【Redis】skiplist跳跃表

    有序集合Sorted Set zadd zadd用于向集合中添加元素并且可以设置分值,比如添加三门编程语言,分值分别为1.2.3: 127.0.0.1:6379> zadd language 1 ...

  7. 【转】浅析SkipList跳跃表原理及代码实现

    SkipList在Leveldb以及lucence中都广为使用,是比较高效的数据结构.由于它的代码以及原理实现的简单性,更为人们所接受.首先看看SkipList的定义,为什么叫跳跃表? "S ...

  8. SkipList 跳跃表

    引子 考虑一个有序表:14->->34->->50->->66->72 从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 ...

  9. 详解SkipList跳跃链表【含代码】

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天继续介绍分布式系统当中常用的数据结构,今天要介绍的数据结构非常了不起,和之前介绍的布隆过滤器一样,是一个功能强大原理简单的数据结构.并且 ...

随机推荐

  1. rocketmq源码分析4-事务消息实现原理

    为什么消息要具备事务能力 参见还是比较清晰的.简单的说 就是在你业务逻辑过程中,需要发送一条消息给订阅消息的人,但是期望是 此逻辑过程完全成功完成之后才能使订阅者收到消息.业务逻辑过程 假设是这样的: ...

  2. 大数据学习——scala类相关操作

    1 类的定义 package com /** * Created by Administrator on 2019/6/3. */ //类并不用声明为public. class Person { // ...

  3. Linux问题故障定位

    CPU 针对应用程序,通常关注的是内核CPU调度器功能和性能. 线程的状态分析主要是分析线程的时间用在什么地方,而线程状态的分类一般分为: a. on-CPU:执行中,执行中的时间通常又分为用户态时间 ...

  4. PHP经典面试题目汇总(上篇)

    1.双引号和单引号的区别 双引号解释变量,单引号不解释变量 双引号里插入单引号,其中单引号里如果有变量的话,变量解释 双引号的变量名后面必须要有一个非数字.字母.下划线的特殊字符,或者用{}讲变量括起 ...

  5. C++之Effective STL学习笔记Item7

    假设我们现在有以下代码: void doSomething() { vector<Widget*> vwp; ; i < SOME_MAGIC_NUMBER; ++i) vwp.pu ...

  6. Codeforces 903F Clear the Matrix

    题目大意 考虑一个 $4$ 行 $n$ ($4\le n\le 1000$)列的矩阵 $f$,$f$ 中的元素为 * 或 . . 对 $f$ 进行若干次如下变换: 将一个 $k\times k$($1 ...

  7. [NOIP2017] 时间复杂度 (模拟,栈)

    题目链接 Solution 用栈进行模拟. 记录一个 \(map\) 来看循环变量有没有用过. 对于每一次入栈都加信息. 出栈直接将 \(top\) 减一下. 反正一堆乱七八糟的东西瞎搞... 注意条 ...

  8. 【2018.12.17】NOI模拟赛4

    题目 WZJ题解 T1 T2 T3 后缀自动机+($parents$ 树)树链剖分 发现有大量子串需要考虑,考虑摁死子串的一端. 首先,这题显然是一道离线题,因为所有的询问都是 $1$ 到 某个数,也 ...

  9. 【bzoj2216】[Poi2011]Lightning Conductor 1D1D动态规划优化

    Description 已知一个长度为n的序列a1,a2,…,an.对于每个1<=i<=n,找到最小的非负整数p满足 对于任意的j, aj < = ai + p – sqrt(abs ...

  10. APUE 学习笔记(五) 进程环境

    1.main函数 C程序总是从main函数开始执行,当内核执行C程序时,在调用main函数之前先调用exec函数从内核获取命令行参数和环境变量值   2.进程终止 正常终止: (1)在main函数内执 ...