原文: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

作者:许强

1. 本博客中的文章均是个人在学习和项目开发中总结。其中难免存在不足之处 ,欢迎留言指正。 2. 本文版权归作者和博客园共有,转载时,请保留本文链接。

跳表SkipList的更多相关文章

  1. 跳表 SkipList

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

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

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

  3. 3.3.7 跳表 SkipList

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

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

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

  5. [转载] 跳表SkipList

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

  6. 跳表(skiplist)Python实现

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

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

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

  8. 跳表(SkipList)原理篇

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

  9. redis笔记_源码_跳表skiplist

    参照:https://juejin.im/post/57fa935b0e3dd90057c50fbc#comment http://redisbook.com/preview/skiplist/dat ...

随机推荐

  1. KindEditor Asp.net

    最近在使用KindEditor,其中遇到三个问题: 1.textarea添加 runat="server" 后整个editor在运行的时候不能显示出来,我没找到原因,于是我就把ru ...

  2. Linux性能监控之Memory篇

    首先说说虚拟内存和物理内存: 虚拟内存就是采用硬盘来对物理内存进行扩展,将暂时不用的内存页写到硬盘上而腾出更多的物理内存让有需要的进程来用.当这些内存页需要用的时候在从硬盘读回内存.这一切对于用户来说 ...

  3. php pdo(二)

    定义:PDO(PHP Data Object)是PHP5才支持的扩展,它为PHP访问各种数据库定义了一个轻量级的.一致性的接口. PDO是PHP5中的一个重大功能,PHP6中将只默认使用PDO来处理数 ...

  4. lamda表达式相关知识

    lamda表达式写法 dt = datado.SelectDalMeath(sqlStr.ToString()); var x = (from r in dt.AsEnumerable() selec ...

  5. Java 容器相关知识全面总结

    Java实用类库提供了一套相当完整的容器来帮助我们解决很多具体问题.因为我本身是一名Android开发者,包括我在内很多安卓开发,最拿手的就是ListView(RecycleView)+BaseAda ...

  6. Spark RDD概念学习系列之RDD的重要内部属性(十五)

    RDD的重要内部属性 通过 RDD 的内部属性,用户可以获取相应的元数据信息.通过这些信息可以支持更复杂的算法或优化. 1)分区列表:通过分区列表可以找到一个 RDD 中包含的所有分区及其所在地址. ...

  7. thymeleaf中的条件判断用法

    一.简单的条件:“if”和“unless” th:if用法实例: <table> <tr> <th>NAME</th> <th>PRICE& ...

  8. Python模块(Module)

    一个Python Module(模块),是一个文件,包含了Python对象定义和Python语句(definitions and statements).文件名就是模块名加上后缀.py,在模块内部,模 ...

  9. ASP.Net自定义重写Http Server标头

    Net中我们为了安全或其他原因起见 可能需要修改我们的标头报文等 以下方法我们通过使用HTTP Module来使用编程的方式来去除或修改它 首先我们自定义一个类CustomServerHeaderMo ...

  10. UVA 11354 Bond(MST + LCA)

    n<=50000, m<=100000的无向图,对于Q<=50000个询问,每次求q->p的瓶颈路. 其实求瓶颈路数组maxcost[u][v]有用邻接矩阵prim的方法.但是 ...