跳表
参考:https://blog.csdn.net/zlx_code/article/details/90581451
1、定义
跳表实际上是一种增加了前向指针的链表,是一种随机化的数据结构,实质上是可以进行二分查找的有序链表;跳表在原来的有序链表上加上了多级索引,通过索引来快速查找;可以支持快速的删除、插入和查找操作。
2、跳表的理解
对于一个单链表来讲,即使链表中存储的数据是有序的,如果我们想要在其中查找某个数据,也只能从头开到尾的遍历,查询效率低,时间复杂度是O(n)。如下图所示
如何提交查找效率,如下图所示,对链表建立一级“索引”,这样查找起来方便了很多,如,每两个节点提取一个节点到上一级的,我们把抽出的那一级叫做索引或者索引层,图中的down表示down指针指向下一级节点。
假如我们现在查找某个节点,比如23,我们先遍历索引层,,当遍历到19的时候,发现下一个节点是25,那么23肯定在19到25这两个节点之间。我们使用down指针,下降到下一级原始链表层,继续遍历,这时候我们只需要遍历2个节点就找到23了,相比于从原始链表开始遍历,要快3个节点。
从上面可以看出,加上一层索引之后,查询效率提高了很多,那么再加上一层呢?我们在第一级索引上再加上第二级索引,建立方式和之前一样,每两个节点抽象出一个节点到第二级索引。现在查找23节点,只需要遍历6个节点就能找到,遍历的次数又减少了。
现在查询效率进一步提升了,现在假如节点变得越来越多,一个有64个节点,建立5个索引层,那么查询第62个节点,在没有建立索引表之前,需要遍历62个节点,建立索引表之后,只需要遍历11个节点,速度提高了很多。所以当链表的长度n比较大的时候,比如1000000,在构建索引之后,查询效率的提升会非常的明显。(摘自极客邦)
3、跳表时间复杂度分析
在一个单链表中查询的时间复杂度是O(n),现在分析一下有n个节点,有多少级索引?
按照每两个节点抽出一个节点作为上一级索引的节点,那么第一级索引节点大约是 n/2 个,第二级的索引大约是 n/4 个,以此类推,第 k 级索引的节点个数是第 k-1 级索引的节点个数的 1/2,那么第 k 级索引点的个数:。
假设索引有 h 级,最高级的索引是 2 个节点,通过上面的例子可以得到:
求得:
如果包含原始链表这一层,那么整个跳表的高度是:。
我们在跳表查询某个数据的时候,如果每一层都要遍历m个节点,那在跳表查询一个数据的时间复杂度是:,m的值是多少呢?
假设我们要找的数据是x,在第 k 级索引中,我们遍历到y节点之后,发现 ,,所以通过y的down指针,从第k级索引到第 k - 1 级索引。在第 k - 1 级索引中,y 和 z 之间只有3个节点,所以我们在第 k - 1 级索引中,最多只遍历3个节点,以此类推,每一级索引最多只需要遍历3个节点。
通过上面的分析,我们得到 m 的值是 3,所以在跳表中查找任意数据的时间复杂度是 O(logn),查找的时间复杂度和二分查找是一样的。
这种查找效率的提升,前提是建立了很多级索引。
4、跳表的空间复杂度分析
假设原始链表的长度是 N,第一级索引大约是 N/2,第二级索引大约是 N/4,以此类推,每一层减少一半,直至剩下两个点,其实就是一个等比数列,计算可以得到:,所以跳表的时间复杂度是 O(n),也就是说如果将n个节点的单链表构成跳表,需要额外将近n个节点的空间,如何降低跳表的存储空间?
前面的分析是使用,每两个节点抽一个节点到上级索引,如果我们使用3个节点或者5个节点:
求和得到的节点,大约是 ,尽管时间复杂度还是O(n),但是存储空间减少了一半。
注意,当原始链表中存储的是很大的对象的时候,而索引节点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引节点大很多的时候,索引节点的存储空间就可以忽略不计了。
5、高效的动态插入和删除
1、插入节点操作
跳表还支持动态的插入和删除,而且插入和删除的时间复杂度是 O(logn)。
在单链表中,一旦定位到要插入的位置,那么插入节点的时间复杂度很低 O(1),但是查找插入位置比较消耗时间。对于单链表而言,需要遍历每个节点,来找插入的位置,而对于跳表而言,查找的时间复杂度是 O(logn),所以查找某个数据应该插入的位置时间复杂度也是 O(logn)。
2、删除节点
如果这个节点在索引中出现,我们除了删除原始链表中的节点,还需要删除索引中的点,在单链表中删除操作是需要拿到待删除节点的前驱节点,然后再删除,如果是双向链表,则没有这个问题了。
6、跳表的动态更新
当我们不停的在跳表中增加数据的时候,如我们不更新索引,那么在极端的情况下,可能出现两个索引节点之间出现数据非常多的情况,极端情况下,退化成单链表:
因此,我们需要维护索引和原始链表之间的平衡,也就是说,如果链表中的节点太多了,那么索引节点也就相应的增加了一些,避免复杂度退化,以及查找,插入和删除的操作。
7、跳表是通过随机函数来维护平衡的
当我们在跳表中插入数据的时候,我们通过选择同时将这个数据插入到部分索引层中,如何选择索引层,可以通过一个随机函数来决定这个节点插入到哪几级索引中,比如随机生成了k,那么就将这个索引加入到,第一级到第k级索引中。
8、总结
跳表使用的是空间换时间的思想,通过构建多级索引来提高查询效率,实现基于链表的“二分查找”,跳表是一种动态的数据结构,支持快速的查找、插入和删除操作,时间复杂度是 O(logn)。
跳表的空间复杂度是 O(n),不过跳表可以通过改变索引策略,动态的平衡执行效率和内存消耗。