跳跃表

跳跃表: 一种基于有序链表的扩展。

基本思想:

查找:

首先链表是有序的,在此基础上,从原有链表中均匀的提取一般的节点组成新的链表。然后,在新的链表上继续均匀提取一半节点作为新链表,直到最终新链表的长度为2. 这样做的目的为: 相当于对有序链表建立索引,每次通过与索引节点比较,可以快速确定查找节点。

 

 

 查找的时间复杂度维 log(N):因为每次提取的新链表长度为前一个链表长度的一半,所以从最上层的链表开始查找,相当于二分查找法的反向过程,二分查找的时间复杂度为logn,所以跳跃表的查找时间复杂度也为logn。 还有一种理解方式:

  查找时间复杂度 = 索引层高度 *  每层比较元素个数。

  索引层高度为: n个元素,按照n/2, n/4, n/8... n/(2^k)的层次划分,第k层元素的个数为n/(2^k),最后一层有两个元素,则2=n/(2^k),得出k = logn-1,即最后一层高度为logn-1,再加上原始的一层,所以总高度为logn。

  每层比较元素的个数:由于每层两个节点限定了上一层(如 2层到1层)的查找只能在这两个节点范围之间进行,而上一层这两个区间的节点最多只有一个,所以每层最多遍历2次,所以 总的时间复杂度为 : logn * 2 = logn.

空间复杂度为 n:每次扩展出来的链表空间依次为:n/2+n/4+n/8+... = n

 

插入: 

插入的具体思想为: 从第一层索引开始,是否需要插入该层采用抛硬币的方法决定是否需要插入,即插入第k层的概率为1/(2^k).如果从某一层由概率算出该层不需要插入,则停止。

  在实际设计时,一般通过一个randomLevel函数直接算出需要插入到第几层,该函数思想为,产生1的概率为,1/2,产生2的概率为1/4,产生k的概率为1/(2^k)。产生出来的值即为需要插入到的层数。这样就可以事先知道需要插入的层数,在查找过程中直接插入。一般可实现为:

// 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且 :
//        1/2 的概率返回 1
//        1/4 的概率返回 2
//        1/8 的概率返回 3 以此类推
private int randomLevel() {
  int level = 1;
  // 当 level < MAX_LEVEL,且随机数小于设定的晋升概率时,level   1
  while (Math.random() < SKIPLIST_P && level < MAX_LEVEL)
    level += 1;
  return level;
}

有如下现象: 一级索引中元素的个数等于 [ 原始链表元素个数 ] * [ randomLevel() 方法返回值 > 1 的概率 ]。因为 randomLevel() 方法返回值 > 1就会建索引,凡是建索引,无论几级索引必然有一级索引,所以一级索引中元素个数占原始数据个数的比率为 randomLevel() 方法返回值 > 1 的概率。对二级索有:二级索引中元素个数占原始数据个数的比率为 randomLevel() 方法返回值 > 2的概率。

由此,SKIPLIST_P 决定了下一层索引元素占上一层元素个数比例,这样概率决定了比例。

如果需要节省空间,SKIPLIST_P可以适当缩小。redis中,起值为1/4.

 

时间复杂度:

  插入一个单链表的时间复杂度为O(1),在最坏的情况下,每一层都要插入,层的高度为logn,则时间复杂为logn。

 

删除:

  单链表的删除时间复杂度为O(1),最坏情况下,每一层都有要删除的元素,时间复杂度为Logn

 

posted on 2020-02-15 11:20  nothing_and  阅读(181)  评论(0)    收藏  举报

导航