跳表SkipList

原文:http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html

跳表SkipList

1.聊一聊跳表作者的其人其事

2. 言归正传,跳表简介

3. 跳表数据存储模型

4. 跳表的代码实现分析

5. 论文,代码下载及参考资料

<1>. 聊一聊作者的其人其事 

跳表是由William Pugh发明。他在 Communications of the ACM June 1990, 33(6) 668-676 发表了Skip lists: a probabilistic alternative to balanced trees,在该论文中详细解释了跳表的数据结构和插入删除操作。

William Pugh同时还是FindBug(没有使用过,这是一款java的静态代码分析工具,直接对java 的字节码进行分析,能够找出java字节码中潜在很多错误。)作者之一。现在是University of Maryland, College Park(马里兰大学伯克分校,位于马里兰州,全美大学排名在五六十名左右的样子)大学的一名教授。他和他的学生所作的研究深入的影响了java语言中内存池实现。

又是一个计算机的天才!

<2>. 言归正传,跳表简介 

这是跳表的作者,上面介绍的William Pugh给出的解释:

Skip lists are a data structure that can be used in place of balanced trees. Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.

跳表是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。

下面来研究一下跳表的核心思想:

先从链表开始,如果是一个简单的链表,那么我们知道在链表中查找一个元素I的话,需要将整个链表遍历一次。

如果是说链表是排序的,并且节点中还存储了指向前面第二个节点的指针的话,那么在查找一个节点时,仅仅需要遍历N/2个节点即可。

这基本上就是跳表的核心思想,其实也是一种通过“空间来换取时间”的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。

<3>.跳表的数据存储模型 

我们定义:

如果一个基点存在k个向前的指针的话,那么陈该节点是k层的节点。

一个跳表的层MaxLevel义为跳表中所有节点中最大的层数。

下面给出一个完整的跳表的图示:

那么我们该如何将该数据结构使用二进制存储呢?通过上面的跳表的很容易设计这样的数据结构:

定义每个节点类型:

// 这里仅仅是一个指针

typedef struct nodeStructure *node;

typedef struct nodeStructure

{

keyType key; // key值

valueType value; // value值

// 向前指针数组,根据该节点层数的

// 不同指向不同大小的数组

node forward[1];

};

上面的每个结构体对应着图中的每个节点,如果一个节点是一层的节点的话(如7,12等节点),那么对应的forward将指向一个只含一个元素的数组,以此类推。

定义跳表数据类型:

// 定义跳表数据类型

typedef struct listStructure{

int level;    /* Maximum level of the list

(1 more than the number of levels in the list) */

struct nodeStructure * header; /* pointer to header */

} * list;

跳表数据类型中包含了维护跳表的必要信息,level表明跳表的层数,header如下所示:

定义辅助变量:

定义上图中的NIL变量:node NIL;

#define MaxNumberOfLevels 16

#define MaxLevel (MaxNumberOfLevels-1)

定义辅助方法:

// newNodeOfLevel生成一个nodeStructure结构体,同时生成l个node *数组指针

#define newNodeOfLevel(l) (node)malloc(sizeof(struct nodeStructure)+(l)*sizeof(node *))

好的基本的数据结构定义已经完成,接下来来分析对于跳表的一个操作。

<4>. 跳表的代码实现分析 

4.1 初始化

初始化的过程很简单,仅仅是生成下图中红线区域内的部分,也就是跳表的基础结构:

list newList()

{

list l;

int i;

// 申请list类型大小的内存

l = (list)malloc(sizeof(struct listStructure));

// 设置跳表的层level,初始的层为0层(数组从0开始)

l->level = 0;

// 生成header部分

l->header = newNodeOfLevel(MaxNumberOfLevels);

// 将header的forward数组清空

for(i=0;i<MaxNumberOfLevels;i++) l->header->forward[i] = NIL;

return(l);

};

4.2 插入操作

由于跳表数据结构整体上是有序的,所以在插入时,需要首先查找到合适的位置,然后就是修改指针(和链表中操作类似),然后更新跳表的level变量。

boolean insert(l,key,value)

register list l;

register keyType key;

register valueType value;

{

register int k;

// 使用了update数组

node update[MaxNumberOfLevels];

register node p,q;

p = l->header;

k = l->level;

/*******************1步*********************/

do {

// 查找插入位置

while (q = p->forward[k], q->key < key)

p = q;

// 设置update数组

update[k] = p;

} while(--k>=0); // 对于每一层进行遍历

// 这里已经查找到了合适的位置,并且update数组已经

// 填充好了元素

if (q->key == key)

{

q->value = value;

return(false);

};

// 随机生成一个层数

k = randomLevel();

if (k>l->level)

{

// 如果新生成的层数比跳表的层数大的话

// 增加整个跳表的层数

k = ++l->level;

// 在update数组中将新添加的层指向l->header

update[k] = l->header;

};

/*******************2步*********************/

// 生成层数个节点数目

q = newNodeOfLevel(k);

q->key = key;

q->value = value;

// 更新两个指针域

do

{

p = update[k];

q->forward[k] = p->forward[k];

p->forward[k] = q;

} while(--k>=0);

// 如果程序运行到这里,程序已经插入了该节点

return(true);

}

4.3 删除某个节点

和插入是相同的,首先查找需要删除的节点,如果找到了该节点的话,那么只需要更新指针域,如果跳表的level需要更新的话,进行更新。

boolean delete(l,key)

register list l;

register keyType key;

{

register int k,m;

// 生成一个辅助数组update

node update[MaxNumberOfLevels];

register node p,q;

p = l->header;

k = m = l->level;

// 这里和查找部分类似,最终update中包含的是:

// 指向该节点对应层的前驱节点

do

{

while (q = p->forward[k], q->key < key)

p = q;

update[k] = p;

} while(--k>=0);

// 如果找到了该节点,才进行删除的动作

if (q->key == key)

{

// 指针运算

for(k=0; k<=m && (p=update[k])->forward[k] == q; k++)

// 这里可能修改l->header->forward数组的值的

p->forward[k] = q->forward[k];

// 释放实际内存

free(q);

// 如果删除的是最大层的节点,那么需要重新维护跳表的

// 层数level

while( l->header->forward[m] == NIL && m > 0 )

m--;

l->level = m;

return(true);

}

else

// 没有找到该节点,不进行删除动作

return(false);

}

4.4 查找

查找操作其实已经在插入和删除过程中包含,比较简单,可以参考源代码。

<5>. 论文,代码下载及参考资料 

SkipList论文

/Files/xuqiang/skipLists.rar

//--------------------------------------------------------------------------------

增加跳表c#实现代码 2011-5-29下午

上面给出的数据结构的模型是直接按照跳表的模型得到的,另外还有一种数据结构的模型:

跳表节点类型,每个跳表类型中仅仅存储了左侧的节点和下面的节点:

我们现在来看对于这种模型的操作代码:

1. 初始化完成了如下的操作:

2. 插入操作:和上面介绍的插入操作是类似的,首先查找到插入的位置,生成update数组,然后随机生成一个level,然后修改指针。

3. 删除操作:和上面介绍的删除操作是类似的,查找到需要删除的节点,如果查找不到,抛出异常,如果查找到的需要删除的节点的话,修改指针,释放删除节点的内存。

代码下载:

/Files/xuqiang/skiplist_csharp.rar

时间: 2024-11-12 09:47:29

跳表SkipList的相关文章

Go语言实现跳表(SkipList)

跳表(skiplist)在redis/levelDB中属于核心数据结构,我简单粗暴的用Golang实现了下. 就我的简单理解来说,就一个普通的链表,在insert时,通过Random_level(),把一层变成很多层, 越上数据越小,跨度越大. 查找时从上往下找,用空间换时间. 记下测试代码: package main import ( "fmt" //"github.com/xclpkg/algorithm" "math/rand" ) fun

存储系统的基本数据结构之一: 跳表 (SkipList)

在接下来的系列文章中,我们将介绍一系列应用于存储以及IO子系统的数据结构.这些数据结构相互关联又有着巨大的区别,希望我们能够不辱使命的将他们分门别类的介绍清楚.本文为第一节,介绍一个简单而又有用的数据结构:跳表 (SkipList) 在对跳表进行讨论之前,我们首先描述一下跳表的核心思想. 跳表(Skip List)是有序线性链表的一种.通常对线性链表进行查找需要遍历,因而不能很好的使用二分查找这样快速的方法(想像一下在链表中定位中间元素的复杂度).为了提高查找速率,我们可以将这些线性链表打散,组

跳表SkipList—定义

1.聊一聊跳表作者的其人其事 2. 言归正传,跳表简介 3. 跳表数据存储模型 4. 跳表的代码实现分析 5. 论文,代码下载及参考资料 <1>. 聊一聊作者的其人其事 跳表是由William Pugh发明.他在 Communications of the ACM June 1990, 33(6) 668-676 发表了Skip lists: a probabilistic alternative to balanced trees,在该论文中详细解释了跳表的数据结构和插入删除操作. Will

C语言跳表(skiplist)实现

一.简介 跳表(skiplist)是一个非常优秀的数据结构,实现简单,插入.删除.查找的复杂度均为O(logN).LevelDB的核心数据结构是用跳表实现的,redis的sorted set数据结构也是有跳表实现的.代码在这里:http://flyingsnail.blog.51cto.com/5341669/1020034 二.跳表图解 考虑一个有序表: 从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 < 2, 4, 6 >,总共比较的次数 为 2 +

跳表 SkipList

跳跃表实现简单,空间复杂度和时间复杂度也较好,Redis中使用跳表而不是红黑树. 实现参考了: 跳跃列表 - 维基百科,自由的百科全书 <Redis设计与实现>第五章 跳跃表 Redis源码3.0分支src/t_zset.c等文件 插入时的核心逻辑: 找到插入的位置 随机得到新插入节点的level 处理为了插入当前节点穿过的指针和未穿过的指针的指向和跨度 删除时的核心逻辑: 找到删除的位置 处理要删除的节点穿过的指针和未穿过的指针的指向和跨度 如果可以,减小跳跃表的level 下面两个题可以使

golang 实现跳表skiplist

package main import ( "math/rand" "time" "fmt" ) const ( P = 0.6 MaxLevel = 8 ) func randomLevel() int { i := 1 rand.Seed(time.Now().UnixNano()) for i < MaxLevel { p := rand.Float64() if (p < P) { i++ } else { break } }

红黑树、B(+)树、跳表、AVL等数据结构,应用场景及分析,以及一些英文缩写

在网上学习了一些材料. 这一篇:https://www.zhihu.com/question/30527705 AVL树:最早的平衡二叉树之一.应用相对其他数据结构比较少.windows对进程地址空间的管理用到了AVL树 红黑树:平衡二叉树,广泛用在C++的STL中.map和set都是用红黑树实现的.我们熟悉的STL的map容器底层是RBtree,当然指的不是unordered_map,后者是hash. B/B+树用在磁盘文件组织 数据索引和数据库索引 Trie树 字典树,用在统计和排序大量字符

K:跳表

??跳表(SkipList)是一种随机化的数据结构,目前在redis和leveldb中都有用到它,它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表, 就能轻松实现一个 SkipList. 考虑一个有序表: 从该有序表中搜索元素 < 23, 43, 59 >,需要比较的次数分别为 < 2, 4, 6 >,总共比较的次数为 2 + 4 + 6 = 12 次. 有没有优化的算法吗?链表是有序的,但不能使用二分查找.类似二叉搜索树,我们把一些节点提取出来

SkipList 跳表

为什么选择跳表 目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等. 想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树 出来吗? 很难吧,这需要时间,要考虑很多细节,要参考一堆算法与数据结构之类的树, 还要参考网上的代码,相当麻烦. 用跳表吧,跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它, 它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表, 就能轻