引子
能够完成动态数据的增删改查最简便的数据结构是什么?
是链表。
链表查询的时间复杂度是O(n)。如何修改最简单的链表,能够让它查得快一点呢?
我们可以多增加一些连接,让它形成网络的结构。
你也可以把它改成一棵树,当然这也可以说是另一种数据结构了。
我们今天要讲跳跃表,skip list。
再增加一张链表,情况会变得很不同。
地铁的例子
某城市的地铁有两条线,一条慢线,它包含所有的车站;一条快线,它的车站是慢线的一个真子集。
数字表示街道号。
慢线位于第二层(最底层),快线在第一层。
比如你要去121街道。
最好的选择是先走快线,走开100街道,然后走慢线,到121街道。
这个就是跳跃表。
跳跃表的第一条规则就是:最底层(在这里我们用L2表示)要包含所有的节点。
那么问题来了:L1(上一层)应该设置几个呢?
L1的理想数值
L1的合理设计是:均匀分布。
既然L1均匀分布,那么查询一个节点的时间就是:
T
=
∣
L
1
∣
+
∣
L
2
∣
∣
L
1
∣
T=|L_1|+\frac{|L_2|}{|L_1|}
T=∣L1∣+∣L1∣∣L2∣
∣ L 1 ∣ |L_1| ∣L1∣表示 L 1 L_1 L1的长度或者说节点数量。
假设最初的节点数量是 n n n。也就是说 ∣ L 2 ∣ = n |L_2|=n ∣L2∣=n,要使得 T T T尽量的小, ∣ L 1 ∣ |L_1| ∣L1∣要取什么值呢?
T = ∣ L 1 ∣ + n ∣ L 1 ∣ ≥ 2 n T=|L_1|+\frac{n}{|L_1|} \geq 2 \sqrt{n} T=∣L1∣+∣L1∣n≥2n
当且仅当 ∣ L 1 ∣ = n |L_1|=\sqrt{n} ∣L1∣=n时取到最小值。
所以完美的跳跃表应该是这个样子的:
让跳跃表更快
如何让跳跃表查得更快?
设计更多的层。
以上的结果是推测的,当然也可以证明。
现在的关键问题是 k k k等于几,使得查询时间最小?
或者说,在已知最底层的数量的情况下( n n n),跳跃表应该设计几层?
所以查询的时间复杂度是 O ( log 2 n ) O(\log_2n) O(log2n)。
重新设计地铁线
按照上面的推理,我们重新设计地铁线。
因为 n = 8 n=8 n=8,所以我们设计三层。
那么层与层之间的比率是多少呢?假设第一层的节点数是是 r r r,于是 r 3 = 8 r^3=8 r3=8,因此第一层有两个节点。第二层我们设置4个节点。