文章目录
1. 什么是跳表
跳表是为了解决"二分查找无法应用到链表"的问题而产生的。
跳表是基于有序的链表实现的,为了提高查找效率,提出对链表加多级“索引”的方式,从而形成跳表。因此,跳表是具有多级索引的链表。如下图所示:
跳表体现了一种以空间换时间的思想,提高了链表的查找效率。
2. 跳表的复杂度分析
2.1 查找的时间复杂度
首先,分析n个结点的链表,有多少级索引。每2个节点抽取抽出一个节点作为上一级索引的节点,那第1级索引的节点个数大约是
n
/
2
n/2
n/2,第2级索引的节点个数大约是
n
/
4
n/4
n/4,依次类推,第k级索引的节点个数就是
n
/
(
2
k
)
n/(2^k)
n/(2k)。假设索引有h级别,最高级的索引有2个节点,则有
n
/
(
2
h
)
=
2
n/(2^h)=2
n/(2h)=2,得出
h
=
l
o
g
2
n
−
1
h=log_2n-1
h=log2n−1,包含原始链表这一层,整个跳表的高度就是
l
o
g
2
n
log_2n
log2n。
然后,对于查找需要看每层遍历多少个元素,假设每层遍历m个元素,则最终查找的时间复杂度为
O
(
m
∗
l
o
g
2
n
)
O(m*log_2n)
O(m∗log2n)。对于每2个结点抽取一个索引的跳表,m最多为3,如下图所示。因此可以查找的时间复杂度就为
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)。
2.2 插入、删除的时间复杂度
跳表不仅支持高校查找,而且支持高效的插入删除。对于一个单链表而言,插入和删除数据的时间复杂度为
O
(
1
)
O(1)
O(1),但是由于要找到插入的位置和删除的元素,因此需要
O
(
n
)
O(n)
O(n)的查找来完成,所以对于链表而言,插入和删除的时间复杂度受限于查找。
而对于跳表而言,插入、删除主要耗时在查找上,因此总时间复杂度等于查找时间复杂度
O
l
o
g
(
n
)
+
O
(
1
)
=
O
l
o
g
(
n
)
Olog(n)+O(1)=Olog(n)
Olog(n)+O(1)=Olog(n)。
这里需要注意,删除的时候需要拿到待删除结点的前驱结点,如果对于单链表,需要一些特殊操作,对于双向链表则不需要考虑这个问题。
2.3 空间复杂度
如果链表有n个节点,每2个节点抽取抽出一个节点作为上一级索引的节点,那每一级索引的节点数分别为:n/2,n/4,n/8,…,8,4,2,等比数列求和n-2,所以跳表的空间复杂度为O(n)。
在实际软件开发中,不必太在意索引占用的额外空间。在讲数据结构和算法时,习惯性地把要处理的数据看成整数,但是在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。
2.4 空间复杂度优化
可以考虑,每3个结点抽取一个索引,这样n/3+n/9+n/27+…+9+3+1=n/2,查找效率稍微低了一点 O ( 4 ∗ l o g ( n ) ) O(4*log(n)) O(4∗log(n)),但是空间占用少了将近一半。
3. 跳表的动态更新
当不停地往跳表中插入数据时,如果不更新索引,就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表。如下图所示:
为了避免这种情况,可以选择插入的同时将这个数据插入到部分索引层。
如何选择这个索引层呢?可以通过随机函数来决定将这个节点插入到哪几级索引中,比如随机函数生成了值K,那就可以把这个节点添加到第1级到第K级索引中。
4. 跳表小结
跳表使用空间换时间的设计思路,通过构建多级索引来提高查询的效率,实现了基于链表的“二分查找”。
跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都是 O(logn), 跳表的空间复杂度是 O(n)。不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
虽然跳表的代码实现并不简单,但是作为一种动态数据结构,比起红黑树来说,实现要简单多了。所以很多时候,为了代码的简单、易读,比起红黑树,更倾向用跳表。
5. 问题思考
5.1 为什么Redias用跳表来实现有序集合而不是红黑树?
严格讲,Redis是用跳表和散列表实现的。Redias支持有序集合的核心操作:
- 插入
- 删除
- 查找
- 按区间查找
- 迭代输出有序序列
Redis用跳表的原因:
- 按区间查找,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。红黑树效率没有跳表高,其余都一样。
- 跳表更容易代码实现。
- 跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
跳表不能完全替代红黑树:
- 因为红黑树较早,很多语言Map就是基于其实现的。
- 红黑树有现成实现,而跳表没有,需要自己写。
5.2 如果3个节点抽取,5个几点抽取一个,则时间复杂度为多少?
都是 O ( l o g n ) O(logn) O(logn),虽然都是 O ( l o g n ) O(logn) O(logn),但是深入分析,其中是有细微差别的,当然无非是时间和空间的相互妥协。