Problem 17-2
(动态二分查找)有序数组上的二分查找花费对数时间,但插入一个新元素的时间与数组规模呈线性关系。我们可以通过维护多个有序数组来提高插入性能。
具体地,假定我们希望支持 n n n元集合上的 S E A R C H SEARCH SEARCH和 I N S E R T INSERT INSERT操作。令 k = ⌈ lg ( n = 1 ) ⌉ k = \lceil \lg(n=1)\rceil k=⌈lg(n=1)⌉,令 n n n的二进制表示为 < n k − 1 , n k − 2 , ⋯ , n 0 > <n_{k-1},n_{k-2},\cdots, n_0> <nk−1,nk−2,⋯,n0>。我们维护 k k k个有序数组 A 0 A_0 A0, A 1 A_1 A1, ⋯ \cdots ⋯, A k − 1 A_{k-1} Ak−1,对 i = 0 , 1 , ⋯ , k − 1 i=0,1,\cdots,k-1 i=0,1,⋯,k−1,数组 A i A_i Ai的长度为 2 i 2_i 2i。每个数组或满或空,取决于 n i = 1 n_i=1 ni=1还是 n i = 0 n_i=0 ni=0。因此所有 k k k个数组中保存的元素总数为 ∑ i = 0 k − 1 n i 2 i = n \sum_{i=0}^{k-1}n_i2^i = n ∑i=0k−1ni2i=n。虽然单独每个数组都是有序的,但不同数组中的元素之间不存在特定的大小关系。
a. 设计算法,实现这种数据结构上的 S E A R C H SEARCH SEARCH操作,分析其最坏情况运行时间。
Answer:
对于
S
E
A
R
C
H
(
x
)
SEARCH(x)
SEARCH(x)操作,我们需要一次在
k
k
k个有序数组中做有序查找,对于满的数组
A
i
A_i
Ai,查找的时间复杂度为
O
(
i
)
O(i)
O(i)。最坏情况下所有数组都为满,且
x
x
x不在任意数组中,则最坏情况时间复杂度为:
T
(
n
)
=
∑
i
=
0
k
−
1
c
i
=
c
⋅
⌈
lg
(
n
+
1
)
⌉
⋅
(
⌈
lg
(
n
+
1
)
⌉
−
1
)
/
2
=
O
(
lg
2
n
)
T(n) = \sum_{i=0}^{k-1}ci = c\cdot \lceil \lg(n+1)\rceil \cdot (\lceil \lg(n+1)\rceil - 1) / 2 = O(\lg^2n)
T(n)=i=0∑k−1ci=c⋅⌈lg(n+1)⌉⋅(⌈lg(n+1)⌉−1)/2=O(lg2n)
b. 设计 I N S E R T INSERT INSERT算法。分析最坏情况运行时间和摊还时间。
Answer:
本题的数据结构实际上是将 n n n转化为二进制表示,该二进制数的位数为 ⌈ lg ( n + 1 ) ⌉ \lceil \lg(n+1)\rceil ⌈lg(n+1)⌉,于数据结构中的数组一一对应,二进制数的该位为1时,对应数组为满,否则为空。因此为了在插入操作时保持数据结构特性,本题化为二进制数的 { 0 , 1 } \{0,1\} {0,1}翻转问题。
当插入一个元素时,新创建一个大小为1的数组 S S S存放该元素。然后进行以下操作:
- 1.找到数据结构中大小与 S S S相同的数组 A lg ∣ S ∣ A_{\lg|S|} Alg∣S∣。
- 2.若数据结构中大小与 S S S相同的数组 A lg ∣ S ∣ A_{\lg|S|} Alg∣S∣为满,则创建新的数组 S S S有序存放原 S S S和 A lg ∣ S ∣ A_{\lg|S|} Alg∣S∣中的元素。该操作时间复杂度度为 O ( ∣ S ∣ ) O(|S|) O(∣S∣)。完成后回到步骤0。
- 3.若数据结构中大小与 S S S相同的数组 A lg ∣ S ∣ A_{\lg|S|} Alg∣S∣为空,则用S替代该数组,插入操作完成。
- 4.若不存在若数据结构中大小与 S S S相同的数组 A lg ∣ S ∣ A_{\lg|S|} Alg∣S∣,直接将 S S S数组作为 A k A_k Ak加入数组集合。
上述操作最坏情况为初始的数组集合
{
A
i
}
\{A_i\}
{Ai}全部为满,此时的最坏情况运行时间为:
T
(
n
)
=
∑
i
=
0
⌈
lg
(
n
+
1
)
⌉
c
⋅
2
i
=
O
(
2
⌈
lg
(
n
+
1
)
⌉
+
1
−
1
)
=
O
(
n
)
T(n) = \sum_{i=0}^{\lceil \lg(n+1)\rceil} c \cdot 2^i = O(2^{\lceil \lg(n+1)\rceil + 1} - 1) = O(n)
T(n)=i=0∑⌈lg(n+1)⌉c⋅2i=O(2⌈lg(n+1)⌉+1−1)=O(n)
下分析摊还时间。从上述分析中可以看出,当数组
A
i
A_i
Ai从空被置为满时,对应的数组
A
j
A_j
Aj要被置满再置空
2
i
−
j
−
1
2^{i-j-1}
2i−j−1次。此处
j
<
i
j < i
j<i。则从
0
0
0开始不断插入元素,插入的总开销小于等于插入
2
⌈
lg
(
n
+
1
)
⌉
+
1
−
1
2^{\lceil \lg(n+1)\rceil + 1} - 1
2⌈lg(n+1)⌉+1−1个元素的开销。在此分析中,与数组
A
i
A_i
Ai相关的开销为被置为满的单次开销乘以置满的次数:
T
(
A
i
)
=
2
i
⋅
(
1
+
∑
j
=
1
k
−
1
−
i
2
j
)
=
2
i
⋅
(
2
k
−
i
−
1
)
=
Θ
(
2
k
)
=
Θ
(
n
)
T(A_i) = 2^i \cdot \left(1 + \sum_{j=1}^{k-1-i}2^{j}\right) = 2^i \cdot (2^{k-i}-1) = \Theta(2^k) = \Theta(n)
T(Ai)=2i⋅(1+j=1∑k−1−i2j)=2i⋅(2k−i−1)=Θ(2k)=Θ(n)
则总开销为:
T
(
n
)
=
∑
i
=
0
k
−
1
T
(
A
i
)
=
k
⋅
Θ
(
n
)
=
Θ
(
n
lg
n
)
T(n) = \sum_{i=0}^{k-1}T(A_i) = k\cdot \Theta(n) = \Theta(n\lg n)
T(n)=i=0∑k−1T(Ai)=k⋅Θ(n)=Θ(nlgn)
故摊还时间为 O ( lg n ) O(\lg n) O(lgn)。
c. 讨论如何实现 D E L E T E DELETE DELETE。
Answer:
与二进制减法相同,假设状态为满的最小下标数组为
A
i
A_i
Ai,则删除操作需要变动的数组仅限被删除的元素本身所在的数组
A
m
A_m
Am以及
A
0
,
A
1
,
⋯
,
A
i
A_0,A_1,\cdots,A_i
A0,A1,⋯,Ai。具体操作为
S
E
A
R
C
H
(
x
)
SEARCH(x)
SEARCH(x),所需时间为
O
(
lg
2
n
)
O(\lg^2n)
O(lg2n)。然后将
A
m
A_m
Am中的
x
x
x替换为
A
i
A_i
Ai中的一个元素,并将该元素从
A
i
A_i
Ai中删除。然后将
A
i
A_i
Ai中剩余的
2
i
−
1
2^i-1
2i−1个元素填入
A
0
,
A
1
,
⋯
,
A
i
−
1
A_0,A_1,\cdots,A_{i-1}
A0,A1,⋯,Ai−1中,最后置空
A
i
A_i
Ai。最坏情况下的总时间复杂度为:
T
(
n
)
=
Θ
(
lg
2
n
)
+
2
k
−
1
=
Θ
(
n
)
T(n) = \Theta(\lg^2n) + 2^k - 1 = \Theta(n)
T(n)=Θ(lg2n)+2k−1=Θ(n)