疯狂创客圈 经典图书 : 《Netty Zookeeper Redis 高并发实战》 面试必备 + 面试必备 + 面试必备 【博客园总入口 】html
疯狂创客圈 经典图书 : 《SpringCloud、Nginx高并发核心编程》 大厂必备 + 大厂必备 + 大厂必备 【博客园总入口 】面试
入大厂+涨工资必备: 高并发【 亿级流量IM实战】 实战系列 【 SpringCloud Nginx秒杀】 实战系列 【博客园总入口 】redis
跳表,是基于链表实现的一种相似“二分”的算法。它能够快速的实现增,删,改,查操做。算法
咱们先来看一下单向链表如何实现查找:编程
链表,相信你们都不陌生,维护一个有序的链表是一件很是简单的事情,咱们都知道,在一个有序的链表里面,查找某个数据的时候须要的时间复杂度为O(n).数据结构
怎么提升查询效率呢?若是咱们给该单链表加一级索引,将会改善查询效率。并发
如图所示,当咱们每隔一个节点就提取出来一个元素到上一层,把这一层称做索引,上层的索引节点都加上一个down指针指向原始节点。高并发
当咱们查找元素11的时候,单链表须要比较6次,而加过索引的两级链表只须要比较4次。当数据量增大到必定程度的时候,效率将会有显著的提高。工具
若是咱们再加多几级索引的话,效率将会进一步提高。这种链表加多级索引的结构,就叫作跳表。
3d
跳表的查询时间复杂度能够达到O(logn)
跳表就是这样的一种数据结构,结点是跳过一部分的,从而加快了查询的速度。跳表跟红黑树又有什么差异呢?既然二者的算法复杂度差很少,为何Redis的有序集合SortedSet要使用跳表实现,而不使用红黑树呢?
Redis 中的有序集合是经过跳表来实现的,严格点讲,其实还用到了散列表。
若是你去查看 Redis 的开发手册,就会发现,Redis 中的有序集合支持的核心操做主要有下面这几个:
插入一个数据;
删除一个数据;
查找一个数据;
按照区间查找数据(好比查找值在 [100, 356] 之间的数据);
迭代输出有序序列。
其中,插入、删除、查找以及迭代输出有序序列这几个操做,红黑树也能够完成,时间复杂度跟跳表是同样的。可是,按照区间来查找数据这个操做,红黑树的效率没有跳表高。对于按照区间查找数据这个操做,跳表能够作到 O(logn) 的时间复杂度定位区间的起点,而后在原始链表中顺序日后遍历就能够了。这样作很是高效。
固然,Redis之因此用跳表来实现有序集合,还有其余缘由,好比,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来讲仍是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它能够经过改变索引构建策略,有效平衡执行效率和内存消耗
假如咱们要查询11,那么咱们从最上层出发,发现下一个是5,再下一个是13,已经大于11,因此进入下一层,下一层的一个是9,查找下一个,下一个又是13,再次进入下一层。最终找到11。
是否是很是的简单?咱们能够把查找的过程总结为一条二元表达式(下一个是否大于结果?下一个:下一层)。理解跳表的查询过程很是重要,试试看查询其余数字,只要你理解了查询,后面两种都很是简单。
接下来看插入,咱们先看理想的跳跃表结构,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。
再讨论删除,删除操做没什么讲的,直接删除元素,而后调整一下删除元素后的指针便可。跟普通的链表删除操做彻底同样。
插入和删除的时间复杂度就是查询元素插入位置的时间复杂度,这不难理解,因此是O(logn)。
疯狂创客圈 - Java高并发研习社群,为你们开启大厂之门