skip list

list的操作:

查找,插入,删除。

 

有序链表

link list

 

上面链表是有序排列的,那么如果想查找某一个元素x,则可以如下操作

 

 

 

如此以来,得到了p之前的元素均小于x,而p之后的元素均不小于x,p是不小于x的第一个元素节点指针.

 

如果要按照顺序插入一个元素x,则可以如下操作

 

 

删除操作,删除不小于x的第一个元素

 

 

以上是顺序链表,而skip list能够在这三个操作上节省时间。将时间由原来的O(n)节省到O(log(n))。

1.如何构建一个skip list?

按照顺序排列的链表作为底层;

将链表中随机抽取一些元素,作为第二层,而第二层的每一个元素均有向下的一个指针,指向第一层中与其相同的元素;

而后再在第二层元素中,随机选取一部分元素,作为第三层,而第三层中的元素也均有向下的指针指向第二层与其相同的元素;以此类推,

直到构建了一层只有最大值最小值(-1,1)指针的元素。如此构建一个skip list。

 

skip list有以下特征:

最下面得一层具有所有元素;

上面每一层的元素均是来自其下面一层的元素;

每层元素均有向下的指针与其下一层相同的元素相关联;

有一个top指针指向最高一层的第一个元素。

 

在skip list中,要实现查找、插入、删除等操作时,只需要操作O(log(n))次。

 

此时的操作方式分别为:

 

查找元素x

 

 

 

插入元素,假设要在第k层插入元素x,k的值不同也会有不同:

 

Determine k the number of levels in which x participates (explained later)
Do find(x), and insert x to the appropriate places in the lowest k levels. (after the elements at which the search path turns down or terminates)
Example - inserting 119. k=2

 

 

 

If k is larger than the current number of levels, add new levels (and update top)
Example - inser(119) when k=4
详见SkipList的PPT,是Arizona的一个guy写的,感觉很NICE,很形象。可惜很多图我无法粘贴。不知道为何。
下面是来自另外一个网站的资料。
目的:向跳跃表中插入一个元素x 
 首先明确,向跳跃表中插入一个元素,相当于在表中插入一列从S0中某一位置出发向上的连续一段元素。有两个参数需要确定,即插入列的位置以及它的“高度”。 
 关于插入的位置,我们先利用跳跃表的查找功能,找到比x小的最大的数y。根据跳跃表中所有链均是递增序列的原则,x必然就插在y的后面。 
 而插入列的“高度”较前者来说显得更加重要,也更加难以确定。由于它的不确定性,使得不同的决策可能会导致截然不同的算法效率。为了使插入数据之后,保持该数据结构进行各种操作均为O(logn)复杂度的性质,我们引入随机化算法(Randomized Algorithms)。 
我们定义一个随机决策模块,它的大致内容如下: 
产生一个0到1的随机数r                            r ← random() 
如果r小于一个常数p,则执行方案A,  if  r<p then do A 
 否则,执行方案B                                     else do B 
初始时列高为1。插入元素时,不停地执行随机决策模块。如果要求执行的是A操作,则将列的高度加1,并且继续反复执行随机决策模块。直到第i次,模块要求执行的是B操作,我们结束决策,并向跳跃表中插入一个高度为i的列。 
性质1: 根据上述决策方法,该列的高度大于等于k的概率为p^(k-1)。 
此处有一个地方需要注意,如果得到的i比当前跳跃表的高度h还要大的话,则需要增加新的链,使得跳跃表仍满足先前所提到的条件。 
三、删除 
 目的:从跳跃表中删除一个元素x 
 删除操作分为以下三个步骤: 
(1) 在跳跃表中查找到这个元素的位置,如果未找到,则退出  * 
(2) 将该元素所在整列从表中删除        * 
(3) 将多余的“空链”删除         * 
【复杂度分析】 
一个数据结构的好坏大部分取决于它自身的空间复杂度以及基于它一系列操作的时间复杂度。跳跃表之所以被誉为几乎能够代替平衡树,其复杂度方面自然不会落后。我们来看一下跳跃表的相关复杂度: 
空间复杂度: O(n)    (期望) 
跳跃表高度: O(logn)  (期望) 
相关操作的时间复杂度: 
 查找:  O(logn)  (期望) 
 插入:   O(logn)  (期望) 
 删除:  O(logn)  (期望) 
  
之所以在每一项后面都加一个“期望”,是因为跳跃表的复杂度分析是基于概率论的。有可能会产生最坏情况,不过这种概率极其微小。 

一、 空间复杂度分析  O(n) 
假设一共有n个元素。根据性质1,每个元素插入到第i层(Si)的概率为pi-1 ,则在第i层插入的期望元素个数为npi-1,跳跃表的元素期望个数为  ,当p取小于0.5的数时,次数总和小于2n。 
所以总的空间复杂度为O(n) 
二、跳跃表高度分析  O(logn) 
根据性质1,每个元素插入到第i层(Si)的概率为p^i ,则在第i层插入的期望元素个数为np^(i-1)。 
考虑一个特殊的层:第1+ 层。 
 层的元素期望个数为 np^0+np^1+...+np^(h-1),当n取较大数时,这个式子的值接近0,故跳跃表的高度为O(logn)级别的。 
三、查找的时间复杂度分析  O(logn) 
我们采用逆向分析的方法。假设我们现在在目标节点,想要走到跳跃表最左上方的开始节点。这条路径的长度,即可理解为查找的时间复杂度。 
设当前在第i层第j列那个节点上。 
i) 如果第j列恰好只有i层(对应插入这个元素时第i次调用随机化模块时所产生的B决策,概率为1-p),则当前这个位置必然是从左方的某个节点向右跳过来的。 
ii) 如果第j列的层数大于i(对应插入这个元素时第i次调用随机化模块时所产生的A决策,概率为p),则当前这个位置必然是从上方跳下来的。(不可能从左方来,否则在以前就已经跳到当前节点上方的节点了,不会跳到当前节点左方的节点) 
设C(k)为向上跳k层的期望步数(包括横向跳跃) 
有: 
C(0) = 0 
C(k) = (1-p)(1+向左跳跃之后的步数)+p(1+向上跳跃之后的步数) 
     = (1-p)(1+C(k)) + p(1+C(k-1)) 
C(k) = 1/p + C(k-1) 
C(k) = k/p 
 而跳跃表的高度又是logn级别的,故查找的复杂度也为logn级别。 
对于记忆化查找(Search with fingers)技术我们可以采用类似的方法分析,很容易得出它的复杂度是O(logk)的(其中k为此次与前一次两个被查找元素在跳跃表中位置的距离)。 
四、插入与删除的时间复杂度分析  O(logn) 
 插入和删除都由查找和更新两部分构成。查找的时间复杂度为O(logn),更新部分的复杂度又与跳跃表的高度成正比,即也为O(logn)。 
 所以,插入和删除操作的时间复杂度都为O(logn) 
最后,概率因子一般用1/2或1/e 

 

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试

关闭