2.3-1
使用图2-4作为模型,说明归并排序再数组 A = < 3 , 41 , 52 , 26 , 38 , 57 , 9 , 49 > A=<3, 41, 52, 26, 38, 57, 9, 49> A=<3,41,52,26,38,57,9,49>上的操作
答:
[
3
9
26
38
41
49
52
57
]
[ 3 \quad 9 \quad 26 \quad 38 \quad 41 \quad 49 \quad 52 \quad 57 ]
[39263841495257]
↑
\uparrow
↑
[
3
26
41
52
∣
9
38
49
57
]
[ 3 \quad 26 \quad 41 \quad 52 | 9 \quad 38 \quad 49 \quad 57 ]
[3264152∣9384957]
↑
\uparrow
↑
[
3
41
∣
26
52
∣
38
57
∣
9
49
]
[ 3 \quad 41 | 26 \quad 52 | 38 \quad 57 | 9 \quad 49 ]
[341∣2652∣3857∣949]
↑
\uparrow
↑
[
3
∣
41
∣
52
∣
26
∣
38
∣
57
∣
9
∣
49
]
[ 3 | 41 | 52 | 26 | 38 | 57 | 9 | 49 ]
[3∣41∣52∣26∣38∣57∣9∣49]
2.3-2
重写过程MERGE,使之不使用哨兵,而是一旦数组L或R的所有元素均被复制回A就立即停止,然后把另一个数组的剩余部分复制回A。
答:
伪代码1:
MERGE(A, p q, r)
n1 = q - p + 1
n2 = r - q
let L[1 .. n1] and R[1 .. n2] be new arrays
for i = 1 to n1
L[i] = A[p + i - 1]
for j = 1 to n2
R[j] = A[q + j]
i = 1
j = 1
for k = p to r
if L[i] <= R[j]
A[k] = L[i]
i = i + 1
else
A[k] = R[j]
j = j + 1
if i > n1 or j > n2
break
while i <= n1
A[k] = L[i]
i = i + 1
k = k + 1
while j <= n2
A[k] = R[j]
j = j + 1
k = k + 1
伪代码2:
MERGE(A, p q, r)
n1 = q - p + 1
n2 = r - q
let L[1 .. n1] and R[1 .. n2] be new arrays
for i = 1 to n1
L[i] = A[p + i - 1]
for j = 1 to n2
R[j] = A[q + j]
i = 1
j = 1
for k = p to r
if i > n1
A[k] = R[j]
j = j + 1
else if j > n2
A[k] = L[i]
i = i + 1
else if L[i] <= R[j]
A[k] = L[i]
i = i + 1
else
A[k] = R[j]
j = j + 1
2.3-3
使用数学归纳法证明:当
n
n
n刚好是2的幂时,以下递归式的解是
T
(
n
)
n
l
o
g
2
n
T(n)nlog_2n
T(n)nlog2n。
T
(
n
)
=
{
2
若
n
=
2
2
T
(
n
2
)
若
n
=
2
k
,
k
>
1
T(n) = \begin{cases} 2 & \text{若}n=2 \\ 2T(\frac{n}{2}) & \text{若}n=2^k,k>1 \end{cases}
T(n)={22T(2n)若n=2若n=2k,k>1
证明:
(1)当
n
=
2
1
n=2^1
n=21时,
T
(
n
)
=
2
=
2
l
o
g
2
2
T(n)=2=2log_22
T(n)=2=2log22。即当
n
=
2
1
n=2^1
n=21时,
T
(
n
)
=
n
l
o
g
2
n
T(n)=nlog_2n
T(n)=nlog2n成立。
(2)假设当
n
=
2
k
n=2^k
n=2k时,
T
(
n
)
=
n
l
o
g
2
n
T(n)=nlog_2n
T(n)=nlog2n成立。即
T
(
n
)
=
T
(
2
k
)
=
2
k
l
o
g
2
k
=
2
k
k
T(n)=T(2^k)=2^klog_2k=2^kk
T(n)=T(2k)=2klog2k=2kk。
(3)当
n
=
2
k
+
1
n=2^k+1
n=2k+1时,
T
(
n
)
=
T
(
2
k
+
1
)
T(n)=T(2^k+1)
T(n)=T(2k+1)
=
2
T
(
2
k
+
1
2
)
+
2
k
+
1
=2T(\frac{2^k+1}{2})+2^{k+1}
=2T(22k+1)+2k+1
=
2
T
(
2
k
)
+
2
k
+
1
=2T(2^k)+2^{k+1}
=2T(2k)+2k+1
=
2
×
2
k
k
+
2
k
+
1
=2\times2^kk+2^{k+1}
=2×2kk+2k+1
=
2
k
+
1
k
+
2
k
+
1
=2^{k+1}k+2^{k+1}
=2k+1k+2k+1
=
2
k
+
1
(
k
+
1
)
=2^{k+1}(k+1)
=2k+1(k+1)
=
2
k
+
1
l
o
g
2
2
k
+
1
=2^{k+1}log_22^{k+1}
=2k+1log22k+1
=
n
l
o
g
2
n
=nlog_2n
=nlog2n
即,当$n=2^{k+1}时,
T
(
n
)
=
n
l
o
g
2
n
T(n)=nlog_2n
T(n)=nlog2n成立。
综上所述,当
n
n
n刚好是2的幂时,以下递归式的解是
T
(
n
)
=
n
l
o
g
2
n
T(n)=nlog_2n
T(n)=nlog2n。
T
(
n
)
=
{
2
若
n
=
2
2
T
(
n
2
)
若
n
=
2
k
,
k
>
1
T(n) = \begin{cases} 2 & \text{若}n=2 \\ 2T(\frac{n}{2}) & \text{若}n=2^k,k>1 \end{cases}
T(n)={22T(2n)若n=2若n=2k,k>1
2.3-4
我们可以把插入排序表示为如下的一个递归过程。为了排序 A [ 1.. n ] A[1 .. n] A[1..n],我们递归地排序 A [ 1.. n − 1 ] A[1 .. n-1] A[1..n−1],然后把 A [ n ] A[n] A[n]插入已排序地数组 A [ 1.. n − 1 ] A[1 .. n-1] A[1..n−1]。为插入排序的这个递归版本的最坏情况运行时间写一个递归式。
答:
T
(
n
)
=
{
Θ
(
1
)
若
n
=
1
,
T
(
n
−
1
)
+
Θ
(
n
)
若
n
>
1
。
T(n) = \begin{cases} \Theta(1) & \text{若}n=1, \\ T(n-1) + \Theta(n) & \text{若}n>1。 \end{cases}
T(n)={Θ(1)T(n−1)+Θ(n)若n=1,若n>1。
2.3-5
回顾查找问题(参见联系2.1-3),注意到,如果序列 A A A已排好序,就可以将该序列的重点与 v v v进行比较。根据比较的结果,原序列中有一半就可以不用再做进一步的考虑了。二分查找算法重复这个过程,每次将序列剩余部分的规模减半。为二分查找写成迭代或递归的伪代码。证明:二分查找的最坏情况运行时间为 Θ ( l o g 2 n ) \Theta(log_2n) Θ(log2n)。
答:
迭代伪代码:
BINARY-SEARCH-ITERATION(A, low, high, v)
while low ≤ high
middle = (low + high) / 2
if v < A[middle]
high= middle - 1
else if v > A[middle]
low = middle + 1
else
return middle
return NIL
迭代C#代码:
private static int BinarySearchIteration(int[] array, int low, int high, int v)
{
while (low <= high)
{
int middle = (low + high) / 2;
if (v < array[middle])
{
high = middle - 1;
}
else if (v > array[middle])
{
low = middle + 1;
}
else
{
return middle;
}
}
return -1;
}
递归伪代码:
BINARY-SEARCH-RECURSION(A, low, high, v)
if low > high
return NIL
middle = (low+ high) / 2
if(v < A[middle])
return BINARY-SEARCH-RECURSION(A, low, middle - 1, v)
else if(v > A[middle])
return BINARY-SEARCH-RECURSION(A, middle + 1, high, v)
else
return middle
递归C#代码:
private static int BinarySearchRecursin(int[] array, int low, int high, int v)
{
if(low > high)
{
return -1;
}
int middle = (low + high) / 2;
if(v < array[middle])
{
return BinarySearchRecursin(array, low, middle - 1, v);
}
else if(v > array[middle])
{
return BinarySearchRecursin(array, middle + 1, high, v);
}
else
{
return middle;
}
}
证明二分查找的最坏情况运行时间为
Θ
(
l
o
g
2
n
)
\Theta(log_2n)
Θ(log2n):
每次我们将
v
v
v与中间元素进行比较时,搜索范围都将继续进行。此时,搜索范围减半。故运行时间的递归式为:
T
(
n
)
=
{
Θ
(
1
)
若
n
=
1
T
(
n
)
=
T
(
n
2
)
+
Θ
(
1
)
若
n
>
1
T(n) = \begin{cases} \Theta(1) & \text{若}n=1 \\ T(n) = T(\frac{n}{2}) + \Theta(1) & \text{若}n>1 \end{cases}
T(n)={Θ(1)T(n)=T(2n)+Θ(1)若n=1若n>1
此递归式的解为
Θ
(
l
o
g
2
n
)
\Theta(log_2n)
Θ(log2n)。
2.3-6
注意到2.1节中的过程INSERTION-SORT的第5~7行的while循环采用一种线性查找来(反向)扫描已排好序的子数组 A [ 1.. j − 1 ] A[1 .. j-1] A[1..j−1]。我们可以是哟个二分查找(参见联系2.3-5)来把插入排序的最坏情况总运行时间改进到 Θ ( n l o g 2 n ) \Theta(nlog_2n) Θ(nlog2n)吗?
答:
在最坏情况中,待排序的元素是按与要求相反的顺序排布的。插入排序算法INSERTION-SORT,在插入第j个元素的迭代中需要做两个步骤:首先,通过线性查找来(反向)扫描已排好序的子数组
A
[
1..
j
−
1
]
A[1 .. j-1]
A[1..j−1];然后将
A
[
1..
j
−
1
]
A[1 .. j-1]
A[1..j−1]中的元素逐个向后移动一个位置。如果使用二分查找,仅仅是改变了查找过程的运行时间,但未改变移动过程的运行时间。
而移动过程的总运行时间依然是
Θ
(
n
2
)
\Theta(n^2)
Θ(n2),因此,我们无法使用二分查找来把插入排序的最坏情况总运行时间改进到
Θ
(
n
l
o
g
2
n
)
\Theta(nlog_2n)
Θ(nlog2n)。
我们也可以通过计算得出类似的结论。
为了简化问题,我们假设将目标数字与数组中某个下标的元素进行对比的时间为1,而移动一个元素的时间也为1。但是,我们不考虑类似操作下标这种不太重要的时间,也不考虑将目标数组放置到数组的指定位置的时间。
在原来的INSERTION-SORT算法中,对于要插入的第
k
k
k个元素,在该次迭代中,比较所需要的运行时间为
k
−
1
k-1
k−1,移动所需要的运行时间也为
k
−
1
k-1
k−1,所需要的运行时间为
2
(
k
−
1
)
2(k-1)
2(k−1)。
所以,总运行时间为
∑
k
=
1
n
(
k
−
1
)
+
(
k
−
1
)
\sum_{k=1}^{n}(k-1)+(k-1)
k=1∑n(k−1)+(k−1)
=
∑
k
=
1
n
2
(
k
−
1
)
=\sum_{k=1}^{n}2(k-1)
=k=1∑n2(k−1)
=
n
(
n
−
1
)
=n(n-1)
=n(n−1)
在改进后的INSERTION-SORT算法中,对于要插入的第
k
k
k个元素,在该次迭代中,比较所需要的运行时间为
l
o
g
2
k
log_2k
log2k,移动所需要的运行时间仍然为
k
−
1
k-1
k−1。
所以,总运行时间为
∑
k
=
1
n
l
o
g
2
k
+
(
k
−
1
)
\sum_{k=1}^{n}log_2k+(k-1)
k=1∑nlog2k+(k−1)
=
n
(
n
−
1
)
2
+
l
o
g
2
(
n
!
)
=\frac{n(n-1)}{2} + log_2(n!)
=2n(n−1)+log2(n!)
因为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)的增长速度大于 n l o g 2 n nlog_2n nlog2n,我们无法使用二分查找来把插入排序的最坏情况总运行时间改进到 Θ ( n l o g 2 n ) \Theta(nlog_2n) Θ(nlog2n)。
通过下图,我们也可以直观地看出这一情况:
*2.3-7
描述一个运行时间为 Θ ( n l o g 2 n ) \Theta(nlog_2n) Θ(nlog2n)的算法,给定 n n n个整数的集合 S S S和另一个整数 x x x,该算法能确定 S S S中是否存在两个其和刚好为 x x x的元素。
答:
首先,使用归并排序算法对集合
S
S
S进行排序,运行时间为
Θ
(
n
l
o
g
2
n
)
\Theta(nlog_2n)
Θ(nlog2n)。
其次,对于排序后的集合
S
S
S中的元素中的每一个元素
s
i
s_i
si(
i
=
1..
n
i = 1 .. n
i=1..n),使用二分查找算法在
S
[
i
+
1..
n
]
S[i+1 .. n]
S[i+1..n]中查找是否有元素
s
i
′
s_i^{'}
si′符合
s
i
′
=
x
−
s
i
s_i^{'}=x-s_i
si′=x−si,这个过程的运行时间是
Θ
(
l
o
g
2
n
)
\Theta(log_2n)
Θ(log2n)。如果找到了,就返回该元素的位置,如果没有找到,则继续下一次迭代。
所以,总的运行时间为:
T
(
n
)
=
Θ
(
n
l
o
g
2
n
)
+
n
⋅
Θ
(
l
o
g
2
n
)
=
Θ
(
n
l
o
g
2
n
)
T(n) = \Theta(nlog_2n)+n\cdot\Theta(log_2n)=\Theta(nlog_2n)
T(n)=Θ(nlog2n)+n⋅Θ(log2n)=Θ(nlog2n)
因此,该算法能确定 S S S中是否存在两个其和刚好为 x x x的元素。