介绍
跳表是一种链表加多级索引的动态数据结构,具体来说是每两个结点提取一个结点到上一级作为索引,如下图所示:
所以跳表相对于普通单链表,查找一个结点所需要遍历的结点个数减少了,也即查找效率提高了。跳表是一种支持二分查找的链表,可以快速地插入、删除、查找,甚至可以替代红黑树。Redis里的有序集合就是用跳表来实现的。
复杂度分析
时间复杂度
在跳表中查找、插入、删除的时间复杂度都是 O ( l o g n ) O(logn) O(logn)。
1.查找的时间复杂度推导
- 查多少层:对于结点个数为n的链表,会有多少级索引?第一级有 n 2 \frac{n}{2} 2n个结点,第二级有 n 2 2 \frac{n}{2^2} 22n个结点,第k级有 n 2 k \frac{n}{2^k} 2kn个结点,最后一级是2个结点,所以通过 n 2 k = 2 \frac{n}{2^k}=2 2kn=2,得到最后一级 k = l o g 2 n − 1 k=log_2n-1 k=log2n−1。如果把原始链表也算作一级,那么最后一级就是 l o g 2 n log_2n log2n,所以一共查 l o g 2 n log_2n log2n层就能查到
- 每层内查几次:最多3次。因为上一层决定了所查找的数在结点j和j+1之间,才会下到下一层去找,而上一层的结点j和j+1之间对应到下一层,只有三个结点
- 总结:跳表的查找时间复杂度为 O ( 3 l o g 2 n ) = O ( l o g n ) O(3log_2n)=O(log n) O(3log2n)=O(logn)
2.插入的时间复杂度推导
单链表的插入:查找
O
(
n
)
O(n)
O(n) + 插入
O
(
1
)
O(1)
O(1)
跳表的插入:查找
O
(
l
o
g
n
)
O(logn)
O(logn) + 插入
O
(
1
)
O(1)
O(1)
3.删除的时间复杂度推导
查找 O ( l o g n ) O(logn) O(logn)+删除 O ( 1 ) O(1) O(1),删除时要注意:记住前驱结点、如果索引中有也要删除索引中的
空间复杂度
跳表的空间复杂度为 O ( n ) O(n) O(n),具体推导如下:
- 在分析时间复杂度时已经提到,对于结点个数为n的链表,会有 l o g 2 n − 1 log_2n-1 log2n−1层索引,各层索引的结点数分别是 n 2 \frac{n}{2} 2n, n 2 2 \frac{n}{2^2} 22n,…, n 2 k \frac{n}{2^k} 2kn,…,2这样的等比数列
- 根据等比数列求和公式:
得到跳表的空间复杂度为 O ( n ) O(n) O(n):
跳表的动态更新
跳表是一种动态数据结构,就是指它的索引是要动态更新的,特别是当插入了很多个结点、或者删除了一些结点时,可能会出现两个索引结点之间的下一层结点很多的情况,造成复杂度退化。
跳表通过随机函数来维护,当往跳表中插入数据的时候,可以选择同时将这个数据插入到部分索引层中,具体插入到从哪一级开始的索引是由一个随机函数生成的。