【算法】数据结构:跳表

菜鸡一只,这次来说另一个数据结构,跳表SkipList!

一、引言

在排好顺序的数组中,要查找某个元素,我们可以通过二分法来快速查找,这其实依赖的是数组的随机访问的特性,我可以通过arr[6],来访问数组中的第七个元素(因为第一个元素下标是0),因此我就可以跳跃着访问数组中任意一个元素。

但如果是普通的链表,第七个元素的地址,我只能通过第六个元素知道,第六个元素的地址我只能通过第五个元素知道,以此类推,我如果要查找链表中的任意一个元素,我都需要从头遍历,就使用不了二分法查找,那么对于链表这样的数据结构的快速查询,是否有合理的解决方案?有的,把链表变为跳表!

二、数据结构

以下这些制作精美的图片

来自于王争老师的《算法与数据结构之美》,我只是搬运工!!!

如果我们在原始链表之上能够加一层索引,查询数据的时候,从索引开始查,那么就能够少遍历一些节点

甚至,我可以再加一层索引

假设我们要查找64个数字中的第62个数

如图所示,原来需要遍历62次,现在只需要遍历11个结点,那在跳表中查询一个数据的时间复杂度就是O(m*logn),m=3(这是公式算出来的,我这里就不具体说了)

 

三、增删

java中跳表有两个支持高并发的类:ConcurrentSkipListSetConcurrentSkipListMap

有一位大神,对源码进行了详细的剖析注释,大家如果有想认真研究,请看下面的链接

死磕 java集合之ConcurrentSkipListMap源码分析——发现个bug(作者:彤哥读源码):

https://zhuanlan.zhihu.com/p/62372686

1、插入数据

总共有三个步骤:

(1)、找到需要插入的位置(这一步就和查找是一样的)

(2)、判断当前节点应该在哪一层开始创建索引,创建索引建立上下节点的连接关系

(3)、建好索引后,将新的索引和以前的索引连接起来

具体代码可以参考ConcurrentSkipListMap的doPut

2、删除数据

删除的话,要考虑这么几个问题:

(1)、删除操作做到一半,是否有其他线程也在做这个操作,同时做的话是否会报错

(2)、不单单是最底下的数据要删除,如果这个数据有在索引中,那么上面的索引也都要删除

(2)、假设删除的数据在最上层索引,并且最上层索引只有他一个值,那么整个跳表结构层级要减一

具体代码可以参考ConcurrentSkipListMap的doRemove

 

四、好奇

有耐心的还是建议认真看看上面那位大神的源码解析,没耐心的看我下面的解释就好了

1、插入数据的时候创建索引的比例是多少?

(rnd & 0x80000001) == 0 

条件为True的概率是多少?

是否创建索引,首先概率肯定不是百分之50(因为代码中创建一个随机值,然后判断随机值是否是正偶数,然而随机值有可能是负奇数,负偶数,正奇数,正偶数),所以大概是百分之25(我还测试了下验证了25%)

运行10W次,结果都在2W5左右

2、计算每个数据出现在的层级的概率是多少

while (((rnd >>>= 1) & 1) != 0)
    ++level;

计算得到每个level的大概比例是多少?

 

可以看到,层级为1的概率基本是50%,层级2的概率是层级1的百分之50,以此类推,(这其实是一个概率题)

 

五、总结

1、时间复杂度

跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都是O(logn)。

2、空间复杂度

跳表的空间复杂度是O(n),也就是说,如果将包含n个结点的单链表构造成跳表,我们需要额外再用接近n个结点的存储空间

但是这就引申出另一方面的思考,如果跳表中本身存的单个数据很大,那么多出的这些空间复杂度,其实只是存了地址(并不实际存储数据),这样看来,其实也不是在任何场景查下都会消耗双倍的资源

3、与红黑树的区别

红黑树的插入、删除、查找操作,时间复杂度也都是O(logn),那么他们之间有什么区别呢?

问:为什么Redis用跳表来实现有序集合,而不用红黑树?

王争老师给出的答案大致如下:

Redis中的有序集合支持的核心操作主要有下面这几个:

(1)、插入一个数据;

(2)、删除一个数据;

(3)、查找一个数据;

(4)、迭代输出有序序列

(5)、按照区间查找数据(比如查找值在[100, 356]之间的数据)

其实问题就出在第(5)条,红黑树不太方便做区间值查找(当然要做肯定是可用的,中序遍历就行,但是数据个数相同的情况下,中序遍历在遍历数据时走过的路线肯定是比跳表长的)

所以为什么数据库在做索引的时候用的是b+树(在最下层的叶子节点上,会通过链表的方式,把所有叶子串起来),目的也是一样的,在遇到区间查找的情况下,能通过查找到的开头的叶子节点,直接往后遍历得到区间中所有的数据(这样就不用走中序遍历绕路了)!

 

 

菜鸡一只,如果有哪里说错的地方,还请大家批评指出(一定坚决改正,做到不误导新人)!

最近也是好久没有写文章了,各种在路上,在去见客户的路上,在往返上海杭州的路上,不过学习还是该学习的,努力还是该努力的,一定要输出的同时也保证输入啊,肚子里没点墨水很快就会被淘汰了!!其实最近真的很想赶快把这些基础的数据结构更新好的,但是苦于时间有限,不光是工作的上的事情,自己想搞的事情也是比较多的,又要踢球运动减肥,又要看看漫画刷刷剧,而且十一也快来了,蠢蠢欲动的回家的心~好啦下次再见拜拜~

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值