跳跃表 -- 随机平衡原理

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.

Enhanced LinkedList

要理解SkipList,得先从LinkedList说起,

LinkedList 增删改查的时间复杂度都是O(N),它最大的问题就是通过一个节点只能reach到下一个节点(Doubly LinkedList 是一种改进方案),那么改进的思路就是通过一个节点reach到多个节点,例如下图,

这种情况下便可将复杂度减小为O(N/2)。这是一种典型的空间换时间的优化思路。

SkipList 更进一步,采用了分治算法随机算法设计。将每个节点所能reach到的最远的节点,两个节点之间看成是一个组,整个SkipList被分成了许多个组,而这些组的形成是随机的。

(顺时针旋转90度看,SkipList其实更像是一个结构^_^)

上图中,[3, 6],[6, 25],[25, 26]是三个大组,而在[6, 25]这个组里面又包含了[6, 9],[9, 17],[17, 25]这三个组,其中还继续细分了下去。当你在SkipList中查找某个节点时,很容易就可以跳过某个分组,这样便大大提升了查找效率。这样的分组方式可以实现二分查找。

每个节点所能reach到的最远的节点是随机的,正如作者所说,SkipList使用的是概率平衡而不是强制平衡。

O(logN)?

既然是随机算法,那怎么能保证O(logN)的复杂度?SkipList作者在论文中有给出了说明,这里从另一个角度说下我的理解。先定义一下,A node that has k forward pointers is called a level k node。假设k层节点的数量是k+1层节点的P倍,那么其实这个SkipList可以看成是一棵平衡的P叉树,从最顶层开始查找某个节点需要的时间是O(logpN),which
is O(logN) when p is a constant。

下面看下Redis与LevelDB中实现SkipList所使用的随机算法。

Redis

t_zset.c中找到了redis使用的随机算法。

[cpp] view plaincopyprint?

  1. /* Returns a random level for the new skiplist node we are going to create.
  2. * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL
  3. * (both inclusive), with a powerlaw-alike distribution where higher
  4. * levels are less likely to be returned. */
  5. int zslRandomLevel(void) {
  6. int level = 1;
  7. while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
  8. level += 1;
  9. return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
  10. }
/* Returns a random level for the new skiplist node we are going to create.
 * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL
 * (both inclusive), with a powerlaw-alike distribution where higher
 * levels are less likely to be returned. */
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

执行level += 1;的概率为ZSKIPLIST_P,也就是说k层节点的数量是k+1层节点的1/ZSKIPLIST_P倍。ZSKIPLIST_P(这个P是作者论文中的p)与ZSKIPLIST_MAXLEVELredis.h中定义,

[cpp] view plaincopyprint?

  1. #define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
  2. #define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
#define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */

所以redis中的SkipList相当于是一棵四叉树。

LevelDB

skiplist.h中找到了LevelDB使用的随机算法。

[cpp] view plaincopyprint?

  1. template<typename Key, class Comparator>
  2. int SkipList<Key,Comparator>::RandomHeight() {
  3. // Increase height with probability 1 in kBranching
  4. static const unsigned int kBranching = 4;
  5. int height = 1;
  6. while (height < kMaxHeight && ((rnd_.Next() % kBranching) == 0)) {
  7. height++;
  8. }
  9. assert(height > 0);
  10. assert(height <= kMaxHeight);
  11. return height;
  12. }
template<typename Key, class Comparator>
int SkipList<Key,Comparator>::RandomHeight() {
  // Increase height with probability 1 in kBranching
  static const unsigned int kBranching = 4;
  int height = 1;
  while (height < kMaxHeight && ((rnd_.Next() % kBranching) == 0)) {
    height++;
  }
  assert(height > 0);
  assert(height <= kMaxHeight);
  return height;
}

(rnd_.Next() % kBranching) == 0)的概率为1/kBranching,所以LevelDB中的SkipList也是一棵四叉树(kBranching = 4;不就是这个意思吗^_^)。

参考资料

时间: 2024-10-28 16:20:56

跳跃表 -- 随机平衡原理的相关文章

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

redis源码分析4---结构体---跳跃表

redis源码分析4---结构体---跳跃表 跳跃表是一种有序的数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的: 跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点.性能上和平衡树媲美,因为事先简单,常用来代替平衡树. 在redis中,只在两个地方使用了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构. 1 跳跃表节点 1.1 层 层的数量越多,访问其他节点的速度越快: 1.2 前进指针 遍历举例

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

本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈“跳跃表”的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类的AVL树,红黑树等要低得多,这使得其

跳跃表Skip List【附java实现】

skip list的原理 Java中的LinkedList是一种常见的链表结构,这种结构支持O(1)的随机插入及随机删除, 但它的查找复杂度比较糟糕,为O(n). 假如我们有一个有序链表如下,如果我们想找到值为59的节点,需要查找7次.怎么提高查询效率呢?通常的做法是使用二分法,但LinkedList的随机访问时间复杂度同样为O(n),因此朴素的二分法并不适用.那怎么办呢? 我们可以在节点中增加额外的跳跃节点,如下: 这样我们可以根据跳跃节点查询,只需要查找3次.至于查询47,我们先根据跳跃节点

查找——图文翔解SkipList(跳跃表)

跳跃表 跳跃列表(也称跳表)是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(logn)平均时间). 基本上,跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机化的方式进行的,所以在列表中的查找可以快速的跳过部分列表元素,因此得名.所有操作都以对数随机化的时间进行. 如下图所示,是一个即为简单的跳跃表.传统意义的单链表是一个线性结构,向有序的链表中插入一个节点需要O(n)的时间,查找操作需要O(n)的时间.如果我们使用图中所示的跳跃表,就可以大大减少减少

“跳跃表”简析

复杂度 空间复杂度: O(n) (期望) 跳跃表高度: O(logn)(期望) 查找:O(logn)(期望) 插入: O(logn)(期望) 删除:O(logn)(期望) 之所以在每一项后面都加一个“期望”,是因为跳跃表的复杂度分析是基于概率论的.有可能会产生最坏情况,不过这种概率极其微小. 顶层链表元素的确定方式 底层链表就是最初的链表,包含所有元素. we just like every node to be accessed sort of as quickly as possible,

第一部分 数据结构与对象 跳跃表

下面是跳跃表的基本原理,REDIS的实现大致相同 跳跃表的一个特点是,插入NODE是通过随机的方式来决定level的,比较奇特 下面是skipList的一个介绍,转载来的,源地址:http://kenby.iteye.com/blog/1187303,为防止源地址丢失,故拷贝一份放在这里,望作者原谅. ---------------转载开始----------------- 为什么选择跳表 目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等. 想象一下,给

跳跃表

1.跳跃表 结构模型(双向链表) L1:某些数据的链表:(相当于快车) L2:底层所有数据的链表:(相当于慢车) L1和L2中键值相同的元素用链表连接起来 2.理想跳跃表   跳跃表的这种数据结构就是二分查找(用链表模拟数组),差不多就是一颗二叉树,但是有太多的重复元素:查找的时间复杂度为:O(logn): 3.跳跃表的插入和删除   保证左上角一直有元素存在,在开始的时候,先放一个负无穷的数字(保证每一层的开始都是这个负无穷的数字):目的:防止很大的数字被提升之后的情况: (1)插入元素x(构

第八课——跳跃表、对象

第八课时作业 静哥 by 2016.4.11~2016.4.17   [作业描述] 1.总结redis在节省内存开销方面做过哪些设计 2.总结概括五种对象的关键点 [作业-1:总结redis在节省内存开销方面做过哪些设计] 关于跳跃表 (1)跳跃表是一种有序数据结构,查询效率和平衡树媲美,实现上比平衡树简单,可以用跳跃表代替平衡树:查找复杂度平均是O(logN)z,最坏是O(N); (2)跳跃表在redis里只有2个地方使用:一个是实现有序集合键(sorted set).一个是集群节点中用作内部