跳表 - 秒懂


JUC 高并发工具类(3文章)与高并发容器类(N文章) :

1.1 跳表图解

跳表,是基于链表实现的一种相似“二分”的算法。它能够快速的实现增,删,改,查操做。算法

咱们先来看一下单向链表如何实现查找:编程

在这里插入图片描述

链表,相信你们都不陌生,维护一个有序的链表是一件很是简单的事情,咱们都知道,在一个有序的链表里面,查找某个数据的时候须要的时间复杂度为O(n).数据结构

怎么提升查询效率呢?若是咱们给该单链表加一级索引,将会改善查询效率。并发

在这里插入图片描述

如图所示,当咱们每隔一个节点就提取出来一个元素到上一层,把这一层称做索引,上层的索引节点都加上一个down指针指向原始节点。高并发

当咱们查找元素11的时候,单链表须要比较6次,而加过索引的两级链表只须要比较4次。当数据量增大到必定程度的时候,效率将会有显著的提高。工具

若是咱们再加多几级索引的话,效率将会进一步提高。这种链表加多级索引的结构,就叫作跳表。
在这里插入图片描述3d

跳表的查询时间复杂度能够达到O(logn)

1.2 为何Redis的有序集合SortedSet要使用跳表实现

跳表就是这样的一种数据结构,结点是跳过一部分的,从而加快了查询的速度。跳表跟红黑树又有什么差异呢?既然二者的算法复杂度差很少,为何Redis的有序集合SortedSet要使用跳表实现,而不使用红黑树呢?

Redis 中的有序集合是经过跳表来实现的,严格点讲,其实还用到了散列表。

若是你去查看 Redis 的开发手册,就会发现,Redis 中的有序集合支持的核心操做主要有下面这几个:

  • 插入一个数据;

  • 删除一个数据;

  • 查找一个数据;

  • 按照区间查找数据(好比查找值在 [100, 356] 之间的数据);

  • 迭代输出有序序列。

其中,插入、删除、查找以及迭代输出有序序列这几个操做,红黑树也能够完成,时间复杂度跟跳表是同样的。可是,按照区间来查找数据这个操做,红黑树的效率没有跳表高。对于按照区间查找数据这个操做,跳表能够作到 O(logn) 的时间复杂度定位区间的起点,而后在原始链表中顺序日后遍历就能够了。这样作很是高效。

固然,Redis之因此用跳表来实现有序集合,还有其余缘由,好比,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来讲仍是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它能够经过改变索引构建策略,有效平衡执行效率和内存消耗

1.3 跳表的查询操做

假如咱们要查询11,那么咱们从最上层出发,发现下一个是5,再下一个是13,已经大于11,因此进入下一层,下一层的一个是9,查找下一个,下一个又是13,再次进入下一层。最终找到11。

在这里插入图片描述

是否是很是的简单?咱们能够把查找的过程总结为一条二元表达式(下一个是否大于结果?下一个:下一层)。理解跳表的查询过程很是重要,试试看查询其余数字,只要你理解了查询,后面两种都很是简单。

1.4 跳表的插入

接下来看插入,咱们先看理想的跳跃表结构,L2层的元素个数是L1层元素个数的1/2,L3层的元素个数是L2层的元素个数的1/2,以此类推。从这里,咱们能够想到,只要在插入时尽可能保证上一层的元素个数是下一层元素的1/2,咱们的跳跃表就能成为理想的跳跃表。那么怎么样才能在插入时保证上一层元素个数是下一层元素个数的1/2呢?很简单,抛硬币就能解决了!

假设元素X要插入跳跃表,很显然,L1层确定要插入X。那么L2层要不要插入X呢?咱们但愿上层元素个数是下层元素个数的1/2,因此咱们有1/2的几率但愿X插入L2层,那么抛一下硬币吧,正面就插入,反面就不插入。那么L3到底要不要插入X呢?相对于L2层,咱们仍是但愿1/2的几率插入,那么继续抛硬币吧!以此类推,元素X插入第n层的几率是(1/2)的n次。这样,咱们能在跳跃表中插入一个元素了。

跳跃表的初试状态以下图,表中没有一个元素:

在这里插入图片描述

若是咱们要插入元素2,首先是在底部插入元素2,以下图:

在这里插入图片描述

而后咱们抛硬币,结果是正面,那么咱们要将2插入到L2层,以下图

在这里插入图片描述

继续抛硬币,结果是反面,那么元素2的插入操做就中止了,插入后的表结构就是上图所示。接下来,咱们插入一个新元素33,跟元素2的插入同样,如今L1层插入33,以下图:

在这里插入图片描述

而后抛硬币,结果是反面,那么元素33的插入操做就结束了,插入后的表结构就是上图所示。接下来,咱们插入一个新元素55,首先在L1插入55,插入后以下图:

在这里插入图片描述

而后抛硬币,结果是正面,那么L2层须要插入55,以下图:

在这里插入图片描述

继续抛硬币,结果又是正面,那么L3层须要插入55,以下图:

在这里插入图片描述

继续抛硬币,结果又是正面,那么要在L4插入55,结果以下图:

在这里插入图片描述

继续抛硬币,结果是反面,那么55的插入结束,表结构就如上图所示。

固然,不可能无限的进行层数增加。除了根据一种随机算法获得的层数以外,为了避免让层数过大,还会有一个最大层数MAX_LEVEL限制,随机算法生成的层数不得大于该值。

以此类推,咱们插入剩余的元素。固然由于规模小,结果极可能不是一个理想的跳跃表。可是若是元素个数n的规模很大,学过几率论的同窗都知道,最终的表结构确定很是接近于理想跳跃表。

固然,这样的分析在感性上是很直接的,可是时间复杂度的证实实在复杂,在此我就不深究了,感兴趣的能够去看关于跳跃表的paper。

1.5 跳表的删除

再讨论删除,删除操做没什么讲的,直接删除元素,而后调整一下删除元素后的指针便可。跟普通的链表删除操做彻底同样。

插入和删除的时间复杂度就是查询元素插入位置的时间复杂度,这不难理解,因此是O(logn)。


回到◀疯狂创客圈

疯狂创客圈 - Java高并发研习社群,为你们开启大厂之门

相关文章
相关标签/搜索