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


在对跳表进行讨论之前,我们首先描述一下跳表的核心思想。

跳表(Skip List)是有序线性链表的一种。通常对线性链表进行查找需要遍历,因而不能很好的使用二分查找这样快速的方法(想像一下在链表中定位中间元素的复杂度)。为了提高查找速率,我们可以将这些线性链表打散,组织成树结构,这样的树就叫做查找树。查找树中尤以平衡查找树的查找代价最小,因此平衡二叉查找树成为了在内存中进行查找的最佳的数据结构。红黑树作为平衡二叉查找树的一种实现,经常被我们用在这种场景之中。

然而,平衡树就一定需要平衡化。在对树进行一系列的插入删除之后,树不再平衡了,此时就需要调整平衡的算法。红黑树的复杂性就体现在这里,相信所有写过红黑树的童鞋都记忆犹新。

就在此时,晴天一个霹雳,跳表诞生了[1]。它支持快速检索且不需要复杂的平衡操作,由于它的简单性,使得它往往比红黑树还有更好的性能表现。

上图就是一个跳表的示意图,图中 (a) 是一个正常的有序列表,而 (b) 到 (e) 则是不同level的跳表。

定义:

对于一个节点,如果它有k个指针,那么就称之为一个 level k 节点。

一个跳表的最大 level 是当前在列表中最大的节点的 level。对于空列表, level为1.

推论:

如果每第 (2i) 个节点有一个指针指向向后数 (2i) 节点,而不是指向紧接着的那个节点,那么所有的节点基本上会满足这样一个分布:50%的节点位于level 1,25%的节点位于level 2,12.5%的节点位于level 3 等等。

如果某个节点的level是随机选择的,且随机所遵守的分布符合上个推论所描述,那么 level k 节点的第 i 个指针只需要指向下一个 level 大于等于 i 的节点就构造如上图所示的跳表,并不需要一定要严格指向第 ii-1 个节点。

算法:

1> 初始化

只创建第一个列表,该列表的leve值就是1。

2> 检索

a) 搜索从最大level的列表开始,找到比要检索的key小的最大的那个元素

b) 如果不能找到,减少一个level继续检索

c) 如果在level 1一层也不能找到,那就说明所要找的元素一定在当前停下的位置的下一个节点

Search(list, key)
{
x = list->header
for (i = list.level; i >= 1; i--)
{
while (x->forward[i]->key < key)
x = x->forward[i]
}
x = x->forward[1]
if (x->key == key) return x->vlaue
else return failure
}

3> 插入

a) 检索要插入的key是否存在,如果存在那么更新value即可

b) 如果不存在,则需要插入。需要注意的是,在检索的过程中,我们应当用一个数组 update[1...MaxLevel] 来保存每一个 level 中搜索停止的节点,这些节点用于后来指向新加入的节点

c) 产生一个新节点,随机的给定该节点的level。如果随机出来的level比当前最大的level还要大,那么扩大 update 数组到新的 MaxLevel 个元素,并且将新增的 update 元素设置为 list 的 header

d) 从 1 开始到 MaxLevel,将新节点的 forward 数据设置为 update[i...MaxLevel] 的值;而将 update[i...MaxLevel] 的 forward[i] 设置为 x.

 insert(list, key, value){
local update[...MaxLevel]
x = list->header
for i = list->level downto do
while x->forward[i]->key < key
x = x->forward[i]
update[i] = x
if x->key == key then x->value = value;
else
lvl = randomLevel()
if lvl > list->level then
for i = list->level + to lvl
update[i] = list->header
list->level = lvl
x = MakeNode(lvl, key, value)
for i = to level
x->forward[i] = update[i]->forward[i];
update[i]->forward[i] = x;
}

> 选择新节点的level

新节点的level完全由一个随机函数产生:

 randomLevel()
lvl =
while random() < p and lvl < MaxLevel do
lvl = lvl +
return lvl

复杂度分析

搜索复杂度的分析方法很简单,结论是非常确定的O(logn),那剩下的比较就是常数项了,如下图所示:

可以看出来,跳表还是有很大的常数项优势的。

-- Reference --

[1] William Pugh, 1990, Communications of the ACM 33.6, Skip Lists: A Probabilistic Alternative to Balanced Trees.

存储系统的基本数据结构之一: 跳表 (SkipList)的更多相关文章

  1. 跳表SkipList

    原文:http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html 跳表SkipList   1.聊一聊跳表作者的其人其事 2. 言归正 ...

  2. 跳表 SkipList

    跳表是平衡树的一种替代的数据结构,和红黑树不同,跳表对树的平衡的实现是基于一种随机化的算法,这样就使得跳表的插入和删除的工作比较简单.     跳表是一种复杂的链表,在简单链表的节点信息之上又增加了额 ...

  3. 3.3.7 跳表 SkipList

    一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下,ConcurrentHashMap 存取速度是ConcurrentSki ...

  4. 跳表(SkipList)原理篇

    1.什么是跳表? 维基百科:跳表是一种数据结构.它使得包含n个元素的有序序列的查找和插入操作的平均时间复杂度都是 O(logn),优于数组的 O(n)复杂度.快速的查询效果是通过维护一个多层次的链表实 ...

  5. 跳表(SkipList)设计与实现(Java)

    微信搜一搜「bigsai」关注这个有趣的程序员 文章已收录在 我的Github bigsai-algorithm 欢迎star 前言 跳表是面试常问的一种数据结构,它在很多中间件和语言中得到应用,我们 ...

  6. redis的zset数据结构:跳表

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 广州这边封闭式管理好久了,今天终于周末可以出去溜溜了 什么是zset z ...

  7. [转载] 跳表SkipList

    原文: http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html leveldb中memtable的思想本质上是一个skiplist ...

  8. C语言跳表(skiplist)实现

    一.简介 跳表(skiplist)是一个非常优秀的数据结构,实现简单,插入.删除.查找的复杂度均为O(logN).LevelDB的核心数据结构是用跳表实现的,redis的sorted set数据结构也 ...

  9. 跳表(skiplist)Python实现

    # coding=utf-8 # 跳表的Python实现 import random # 最高层数设置为4 MAX_LEVEL = 4 def randomLevel(): ""& ...

随机推荐

  1. springboot中filter的用法

    一.在spring的应用中我们存在两种过滤的用法,一种是拦截器.另外一种当然是过滤器.我们这里介绍过滤器在springboot的用法,在springmvc中的用法基本上一样,只是配置上面有点区别. 二 ...

  2. ACM刷题踩坑记录

    2017-12-26: 1.再次被写法坑了好长一会,调了半天的bug,还是没找出来.最后,发现,又坑在这个小细节上了.这样子写,第一个if和第三个else在一次循环中都会执行,然后,就GG了. 要注意 ...

  3. 基于C++11实现的线程池

    1.C++11中引入了lambada表达式,很好的支持异步编程 2.C++11中引入了std::thread,可以很方便的构建线程,更方便的可移植特性 3.C++11中引入了std::mutex,可以 ...

  4. 简单的TCP代理服务器

    我之前的一篇文章(http://www.cnblogs.com/MikeZhang/archive/2012/03/07/socketRedirect.html )中介绍过用python写的一个简单的 ...

  5. Cascade属性的取值

    Cascade属性的取值有:1.none:忽略其他关联的对象,默认值.2.save-update:当session通过save(),update(),saveOrUpdate()方法来保存或更新对象时 ...

  6. JSP九大对象

    内置对象(又叫隐含对象,有9个内置对象):不需要预先声明就可以在脚本代码和表达式中随意使用 JSP中九大内置对象为: request——请求对象——类型 javax.servlet.ServletRe ...

  7. shell脚本学习指南-学习(2)

    1.I/O重定向符:<   >  >与管道   | #! /bin/bash echo -n "Enter your name!" //输出 printf &qu ...

  8. interrupt和isInterrupted的基本使用方法

    java线程是协作式,而非抢占式 调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定. ...

  9. php模拟http请求

    在http简析中,我们提到了浏览器请求资源的一个流程,那么这个流程能不能用php来模拟呢?答案是肯定的. php模拟http请求需要实现以下步骤: 1.连接apache服务器 使用fsockopen: ...

  10. Hadoop2.2.0多节点分布式安装及测试

    众所周知,hadoop在10月底release了最新版2.2.很多国内的技术同仁都马上在网络上推出了自己对新版hadoop的配置心得.这其中主要分为两类: 1.单节点配置 这个太简单了,简单到只要懂点 ...