7-1
a
可以知道,此时支点值 为 。
b
当数组中所有元素都相等时,HOARE-PARTITION
将会返回 ,也就是中间值。这个算法相比于普通的 PARTITION
,其将各个元素划分地更加均匀,使得快速排序算法的时间复杂度得以下降至最好。
c
一开始时, 成立,恰好在数组的左侧和右侧。直到开始访问数组时,数组指针 先向中间靠拢。当 时, 将一直左移;当 时, 将一直右移。由于一个数 不可能同时大于一个数 ,并且小于 ,因此当 相遇时,它们必定停下来。最终, 永远不会越界。
d
根据题目 7-1-c 的结论, 将会一直在区间 内,哪怕是已经达到 。由于 ,那么将会有以下 种情况说明 :
,此时第一轮 while
循环中,两个 repeat
循环各迭代一次后循环立刻停下。交换完成后,由于 仍然成立,因此外循环仍然不会结束。第二轮循环迭代将会使得 再自减 ,由此 。
,此时第一轮 while
循环中,第一个 repeat
循环将会迭代两次以上,那么就有 仍然成立。
因此,返回的 仍然保证了在区间 中。
e
在每一次迭代前, ,都有 成立, ,都有 成立。当遇到不满足这个情况的数时,两个 repeat
循环都会停下,交换这两个分别不符合各自性质的数,从而维持着它们原本的性质。因此,最终循环终止后, ,都有 仍然成立, ,都有 仍然成立。因此, 中的所有数都不超过 。
f
1 2 3 4 5 6 HOARE-QUICKSORT(A, p, r) if p < r q = HOARE-PARTITION(A, p, r) HOARE-QUICKSORT(A, p, q) HOARE-QUICKSORT(A, q + 1, r)
7-2
a
由于数组中的所有数都一样,算法 RANDOMIZED-PARTITION
的前 2 行将会不会起到任何作用。算法 RANDOMIZED-PARTITION
调用算法 PARTITION
时,PARTITION
将会把所有数都划分到左侧,并且总返回 。因此其运行时间为 。
b
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 返回的两个下标q, t代表着A[p : q - 1], A[q : t], A[t + 1 : r]这三段。 PARTITION'(A, p, r) x = A[p] i = k = p j = r while i <= j if A[i] < x exchange A[i] and A[k] i += 1 k += 1 else if A[i] == x i += 1 else exchange A[i] and A[j] j -= 1 return k, i - 1
可以发现,在每一轮循环中,要么 自增 ,要么 自减 ,直到 相遇。因此算法 PARTITION'
的时间复杂度为 。
c
1 2 3 4 5 6 7 8 9 10 RANDOMIZED-PARTITION'(A, p, r) i = RANDOM(p, r) exchange A[r] with A[i] return PARTITION'(A, p, r) QUICKSORT'(A, p, r) if p < r (q, t) = RANDOMIZED-PARTITION'(A, p, r) QUICKSORT'(A, p, q - 1) QUICKSORT'(A, t + 1, r)
d
为了方便分析,考虑将算法 PARTITION'
中将 和 的两次比较统一称为一次比较,因为将 和 仅需一次比较,就可以得出是小于,等于,还是大于,而为了方便编写伪代码,这里写成了两次比较(在第 5,
8 行分别进行了一次小于和等于的比较。)
假设 是数组 中不同的数的个数,那么算法 RANDOMIZED-PARTITION'
的调用次数至少为 。
假设数 在 中的出现次数为 。假设数组 排序后的结果为 ,满足 。
对于 中的两个数 ,如果 ,那么它们之间有可能发生比较,有可能没有发生比较(取决于支点 是否选择自 或者是 ,如果是,那么就发生了比较。)无论如何,为 的这一些数将会发生 次比较。因此,相同的比较次数为 次。
现在假设 ,那么如果数 满足 先于 被选择,那么 将会无法被比较,否则可以被比较。这些 的个数有上限 个。
和第 7.4.2 章类似,假设 是比较次数, 是元素 被比较的概率,那么有 ,而不再是等于。
因此,有
其中,步骤 是因为当 时, 成立。
这说明算法 RANDOMIZED-PARTITION'
的时间复杂度为 。
根据算法 QUICKSORT'
的第 3, 4 行递归的区间可知,其上限 必定不超过算法 RANDOMIZED-QUICKSORT
的上限。
因此,最终有 。
7-3
a
根据算法 RANDOMIZED-PARTITION
的第一行可以知道,每个元素被选作支点的概率为 。那么可以得到 。
b
可以发现,在每次随机选择支点中,最多只有一个支点被选中,因此最多只有一个 。假设此时选择的支点是第 小的,那么就有 个元素划分至左侧,有 个元素划分至右侧,产生了大小分别为 的子问题。对整个数组进行一次划分的时间复杂度为 ,因此有
c
假设 ,因为空数组并不会产生任何的开销。由于随机变量 和划分后的子问题是独立的,那么有
d
可以发现, 是一个递增函数,因此有
当 时,可以验证原式仍然成立。因此 ,都有 。
e
假设 ,那么联立题目 7-3-c, 7-3-d 的结论,有
其中,变换 表示假设 渐进增长上界比 高。
因此,只需要 再取好 。那么由数学归纳法, 成立。
7-4
a
考虑使用归纳法的思想证明,将数组前 ,中间 ,后 的区间分别称为数组的左侧,中间,右侧。
当数组长度 时,可以发现这个算法能够正确排序。
当 时,第 1,
2 行的代码保证了头尾两个元素是有序的。第 5 行代码排序完成后,左侧在 中最大的 个元素将会转移到中间。第 6 行代码执行前,可以知道最大的 元素要么在中间,要么在右侧。运行完成后,最大的 个元素都在右侧且有序,此时最小的 个元素均在左侧或中间。执行完成第 7 行代码后,左侧和中间共同保持有序,它们和右侧元素同样保持有序。
此时,算法 STOOGE-SORT
使得序列 有序。
b
假设算法 STOOGE-SORT
的运行时间为 ,那么根据第 5, 6,
7 行的递归,可以发现产生了 个大小为 的子问题,并且每次调用所需要花费的其它操作时间为 。因此可以写出递推式: 。
考虑使用主定理,代入得到 。
那么使用第一个条件,可以得到 。
c
不能,因为算法 STOOGE-SORT
的最坏时间复杂度比以上所述所有算法的最坏时间复杂度都要糟糕。
7-5
a
考虑使用归纳法进行说明。
可以发现,对于长度为 及以下的数组,它们可以正确的被排序。
当长度 时,考虑进行划分。每一次划分完成后,支点 已经到达了正确的已排序位置。并且递归排序数组 中的部分。根据上面的假设,数组 最终已经正确排序,并且 是数组中最小的 个数。在下一次迭代得到新的划分 时,支点 已经到达了正确的位置,最终正确地递归排序 中的数…… 因此循环结束后,整个数组将被正确地排序。
b
当整个数组是已排序的时候,递归的深度可以达到 。每次划分,数组右侧将会没有元素,而左侧却有 个。
c
每一次递归调用,参数 将会满足 的值减少到原来至多一半,因此递归调用的最大深度为 。可以发现,这份代码总体结构并没有改变,仍然保持着 的运行时间。
1 2 3 4 5 6 7 8 9 10 TRE-QUICKSORT(A, p, r) while p < r // Partition and then sort the low side. q = PARTITION(A, p, r) if q <= ⌊(p + r) / 2⌋ TRE-QUICKSORT(A, p, q – 1) p = q + 1 else TRE-QUICKSORT(A, q + 1, r) r = q - 1
7-6
a
假设这个数组的 个元素都不相同,随机选取 个数,那么有 种选法。其中,第 小的数成为最终的支点的支点的选法有 种,因为比 小的数有 种选法,比 大的数有 种选法。因此,有
b
按照这个算法,序列 的中位数被选上的概率增加了 ,也就是:
尝试计算极限 ,有
c
令 ,那么可以得到
那么最终结果为 。
可以得到 。
d
由于最完美的快速排序算法仍然会有 的下界。这里介绍的划分算法不如最完美的划分算法优秀,偶尔会引入比较坏的情况,因此仍然会有 的下界。
7-7
本题的思想参考了题目 7-2。假设区间 是一部分区间的交集,那么将包含区间 的所有区间放数组 中间(因为这一些区间全部可以赋予 中的同一个数),右端点在 左边的区间放在数组 左侧,左端点在 右侧的区间放在数组 右侧,由此递归地排序。
a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // 判断区间A和B是否相交。 IS-INTERSECT(A, B) return max{A.l, B.l} <= min{A.r, B.r} // 区间A和B之交。 INTERSECT(A, B) return (max{A.l, B.l}, min{A.r, B.r}) // 这个划分方案将区间划分成三部分:A[p : q - 1], A[q : t], A[t + 1 : r]。其中$A[q : t]$中的所有数全都可以赋值同一个数。 PARTION-INTERVAL(A, p, r) i = RANDOM(p, r) exchange A[i] with A[p] I = A[p] i = k = p j = r while i <= j if IS-INTERSECT(I, A[i]) I = INTERSECT(I, A[i]) i += 1 else if A[i].b < I.a exchange A[i] and A[k] i += 1 k += 1 else exchange A[i] and A[j] j -= 1 return k, i - 1 QUICKSORT-INTERVAL(A, p, r) if p < r (q, t) = PARTION-INTERVAL(A, p, r) QUICKSORT-INTERVAL(A, p, q - 1) QUICKSORT-INTERVAL(A, t + 1, r)
b
如果这些区间是尽可能的重叠,那么它们将会很容易被算法 PARTION-INTERVAL
求出它们的交集 。最终这些区间都被放在了中间,达到了递归终点。算法 QUICKSORT-INTERVAL
将会忽略掉这些已经安排好的区间,如果足够重叠,忽略的区间数足够多,那么就有可能全部忽略完,达到 的时间复杂度。
算法 QUICKSORT-INTERVAL
最终递归求解的是自问 ,这两个子问题并不重叠。在最坏情况下,这些区间互相不相交,此时这个算法相当于一般的 RANDOMIZED-QUICKSORT
算法,其时间复杂度为 。在一般情况下,这个算法的行为和 RANDOMIZED-QUICKSORT
相同,其时间复杂度为 。