基础算法与数据结构——跳表

一、跳表定义

    是一种随机(跳跃点随机)且有序(数据有序)数据结构,它每个节点包含着多个指针,用来指向其它节点的,跳表就是基于此来实现其功能–跳着查找,它的平均插入以及查找的性能都为O(logn),可用于代替平衡树的一种数据结构。
基础算法与数据结构——跳表_第1张图片
    跳表于1990年由威廉·普发明,其对跳表的评价是:“跳跃列表是在很多应用中有可能替代平衡树而作为实现方法的一种数据结构。跳跃列表的算法有同平衡树一样的渐进的预期时间边界,并且更简单、更快速和使用更少的空间。”

二、跳表的思想

    跳表之所以性能快,是因为采取了建立索引的典型思路,我们知道,在普通的顺序表中,上一个节点只有指向下一个节点的指针,这样只能保证从上一个节点按部就班的遍历到下一个节点,但是能不能每个指针含有多个指向别的节点的指针,这样就能跳过某些节点遍历来达到换取时间开销了,跳表就是这样的思想。

    在跳表的遍历中,会从最高层开始遍历,迭代指针碰到空指针或者当前值大于遍历的值时,都会进入下一层接着遍历。而在最下层时,跳跃表就降级为普通的链表了,这也保证了跳跃表在时间性能上最差以O(n)的复杂度遍历到需要遍历到的值,比如下图描述了查找3这个值的遍历顺序(红色线):
基础算法与数据结构——跳表_第2张图片

三、跳表的操作

    跳表的建立是建立在索引上的,一般建立索引有两种方式,随机索引与固定索引。随机索引在跳表中应用的多一些,所以这篇博客以随机索引的方式建立跳表。

    一般以不确定最大层数来构建跳表,对于每个跳表节点,采取一种抛硬币的方式来得到该节点的层数:

randomLevel()
    level := 1
    // random()返回一个[0...1)的随机数
    while random() < p and level < MaxLevel do
        level := level + 1
    return level

    伪代码中,p表示一种概率,MaxLevel为最大层数,假如p的值为0.5,那么该节点层数为1的概率为1,为2的概率为0.5,为3的概率为0.5*0.5=0.25,就这样层数越高,概率越低。这样保证了高层以较低概率出现,保证了跳表的性能,因为节点很少,但是层数很高的情况下,会造成性能的丢失(从高层遍历到底层的性能开销)。

    在遍历到新节点插入的合适位置时,从首节点开始,以新节点的最高层数为起点,开始遍历,需要用一个数组来存需要更新指针的前节点,这样就不必重复遍历了:

基础算法与数据结构——跳表_第3张图片
    从图中可以看出待更新的节点层数有两种情况:

  • 指针后续直接指向空指针或者指向了大于新节点的值。
  • 新节点的前一节点低于等于新节点的层数。

基础算法与数据结构——跳表_第4张图片

四、跳表的应用场景与性能

    跳表因为有序这一特性,以及平均插入、查找、删除时间复杂度为O(logn)这一特性,所以经常用于替代红黑树来实现排行榜这一功能。在Java中,ConcurrentSkipListMap采用了跳表的实现,ConcurrentSkipListMapmap有序的实现,在无序环境下应当用ConcurrentHashMap,而其后者在无序的情况下并发度比前者好。而在Redis中,zset集合也使用了跳表的实现。与红黑树的比较如下:

  • 相较于红黑树来说,跳表的实现非常简单,且没有多余的旋转操作
  • 比起红黑树占用更多的内存,因为要存贮其它的节点信息
  • 两者时间复杂度差别不大,跳表的并发性要比红黑树要好

你可能感兴趣的:(数据结构/算法)