Problem 1 (教材习题 7-1)
(Hoare 划分的正确性) 第6讲PPT第6页的 PARTITION 算法并不是其最初的版本。下面给出的是最早由 C.R.Hoare 所设计的划分算法:
- 试说明 HOARE-PARTITION 在数组 A=⟨13,19,9,5,12,8,7,4,11,2,6,21⟩ 上的操作过程,并说明在每一次执行第 4 到 13 行 while 循环时数组元素的值和辅助变量的值。
后续的三个问题要求读者仔细论证 HOARE-PARTITION 的正确性。在这里假设子数组 A[p..r] 至少包含 2 个元素,试证明下列问题:
下标 i 和 j 不会使我们访问在子数组 A[p..r] 以外的数组 A 的元素。
当 HOARE-PARTITION 结束时,它返回的值 j 满足 p≤j<r 。
当 HOARE-PARTITION 结束时, A[p..j] 中的每一个元素都小于或等于 A[j+1..r] 中的元素。
在第6讲PPT第6页的 PARTITION 过程中,主元(原来储存在 A[r] 中)是与它所划分的两个分区分离的。与之对应,在 HOARE-PARTITION 中,主元(原来储存在 A[p] 中)是存在于分区 A[p..j] 或 A[j+1..r] 中的。因为有 p≤j<r ,所以这一划分总是非平凡的。
- 利用HOARE-PARTITION ,重写 QUICKSORT 算法。
Solution
调用 HOARE-PARTITION(A,1,12) ,过程略,第 4 到 13 行的 while 循环结束后, x=13,i=10,j=9 。
首先,第一次循环开始前,有 i=p−1,j=r+1 。第一次循环过程中,根据算法第 5 到 10 行的两个 repeat-until ,由于我们是先执行下标更新,后执行数组访问,第二次循环开始前,一定有 i≥p,j≤r ,所以第一次循环不会越界。并且,根据算法第 12 到 13 行的判断条件,如果 i≥j 则循环终止。因此,对于算法第 4 到 13 行的 while 循环,从第二次循环开始,每次迭代之前都会有不变式: p≤i<j≤r ,这可以很容易地证明。
首先, j≥p 是显然的,这是第 2 问结论的一部分,我们只需要证明 j<r 即可。如果第 4 到 13 行的 while 循环运行了不止一次,那么 j<r 是显然的,因为每次迭代至少会运行一次 j=j−1 。如果只运行了一次,由于 A[p]=x ,因此第一次迭代一定会 i=p ,迭代停止说明 j≤i=p<r ,这里 p<r 是因为 ∣A∣≥2 。
使用循环不变式来证明,不变式为: A[p..i] 中的所有元素都小于等于 x ,A[j..r] 中所有的元素都大于等于 x 。这个不变式不难证明,这里不再赘述。当最后一次迭代结束时, i≥j ,于是 A[p..j] 中的元素都小于等于 A[j+1..r] 中的元素。其中,如果 i>j ,则 A[i..j] 之间的元素都等于 x ,不过这并不影响最重的结论。
只需要注意 Hoare 划分中主元并没有被单独拎出来就可以了。
Problem 2 (教材习题 7-2)
(针对相同元素值的快速排序) 在第6讲PPT第28页对随机化快速排序的分析中,我们假设输入元素的值是互异的,在本题中,我们将看看如果这一假设不成立会出现什么情况。
如果所有输入元素的值都相同,那么随机化快速排序的运行时间会是多少?
PARTITION 过程返回一个数组下标 q ,使得 A[p..q−1] 中的每个元素都小于或等于 A[q] ,而 A[q+1..r] 中的每个元素都大于 A[q] 。请修改 PARTITION 代码来构造一个新的 PARTITION’(A,p,r),它排列 A[p..r] 的元素,返回值是两个数组下标 q 和 t ,其中 p≤q≤t≤r ,且有
A[q..t] 中的所有元素都相等。
A[p..q−1] 中的每个元素都小于 A[q] 。
A[t+1..r] 中的每个元素都大于 A[q] 。
与 PARTITION 类似,新构造的 PARTITION’ 的时间复杂度是 Θ(r−p) 。
将 RANDOMIZED-PARTITION 过程修改为调用 PARTITION’ ,并重新命名为 RANDOMIZED-PARTITION’。请修改 QUICKSORT 的代码构造一个新的 QUICKSORT’(A, p, r) ,它调用 RANDOMIZED-PARTITION’ ,并且只有分区内的元素互不相同的时候才做递归调用。
在 QUICKSORT’ 中,应该如何改变第6讲PPT第28页中的分析方法,从而避免所有元素都是互异的这一假设?
Solution
由于所有的输入元素都是相同的, RANDOMIZED-PARTTION 算法每次都会返回 q=r ,从而快速排序运行时间的递归式为 T(n)=T(n−1)+Θ(n) ,因此 T(n)=Θ(n2) 。
算法伪代码如下,不难看出其时间复杂度为 Θ(r−p) 。
- 算法伪代码如下:
- 由于我们在 QUICKSORT’ 中并没有对相同的元素进行递归调用,因此 QUICKSORT’ 中的子问题规模不会超过 QUICKSORT ,因此运行时间并不会超过所有元素都互异的情况,即 Θ(nlogn) 。所以分析 QUICKSORT’ 就不需要再作所有元素都互异的假设了,直接和 QUICKSORT 比较即可。
Problem 3 (教材习题 7-3)
(另一种快速排序的分析方法) 对随机化版本的快速排序算法,还有另一种性能分析方法,这一方法关注每一次单独递归调用的期望运行时间,而不是比较的次数。
证明:给定一个大小为 n 的数组,任何特定元素被选为主元的概率为 1/n 。利用这一点来定义指示器随机变量 Xi=I{Hi} ,其中事件 Hi 表示“第 i 小的元素被选为主元为事件”, E[Xi] 是什么?
设 T(n) 是一个表示快速排序在一个大小为 n 的数组上运行时间的随机变量,试证明:
E[T(n)]=E[q=1∑nXq(T(q−1)+T(n−q)+Θ(n))]
- 证明第2问中的公式可以重写为:
E[T(n)]=n2q=2∑n−1E[T(q)]+Θ(n)
- 证明下面的等式(提示:可以将累加式分成两个部分,一部分是 k=2,3,⋯,⌈n/2⌉−1 ,另一部分是 k=⌈n/2⌉,⋯,n−1 )。
k=2∑n−1klogk≤21n2logn−81n2
- 利用第 4 问中给出的界来证明:第 3 问中的递归式有解 E[T(n)]=O(nlogn) 。(提示:使用代入法,证明对于某个正常数 a 和足够大的 n ,有 E[T(n)]≤anlogn 。)
Solution
- 根据 RANDOMIZED-PARTITION 算法(见第6讲PPT第24页)第 2 行,主元是在 n 个元素当中随机选取的,因此 Pr{Hi}=1/n 。根据引理5.1(见第4讲PPT第10页),有
E[Xi]=E[I{Hi}]=Pr{Hi}=n1
当 Hq 发生时,原本的排序问题被划分为两个规模为 q−1 和 n−q 的子问题,当然,划分过程需要 Θ(n) 的时间,此时,期望上,T(n)=E[T(q−1)+T(n−q)+Θ(n)] ,其中 i=1,2,⋯,n 。根据数学期望的定义,我们有
E[T(n)]=q=1∑nPr{Hq}⋅E[T(q−1)+T(n−q)+Θ(n)]=q=1∑nE[Xq]E[T(q−1)+T(n−q)+Θ(n)]=q=1∑nE[Xq(T(q−1)+T(n−q)+Θ(n))]=E[q=1∑nXq(T(q−1)+T(n−q)+Θ(n))]
其中,倒数第 2 步使用的是事件的独立性,第 1 步使用的是期望的线性性质。
推导过程如下(其中最后一步是因为 T(1)=O(1) ):
E[T(n)]=q=1∑nE[Xq]E[T(q−1)+T(n−q)+Θ(n)]=q=1∑nn1E[T(q−1)+T(n−q)+Θ(n)]=Θ(n)+n1q=1∑nE[T(q−1)]+E[T(n−q)]=Θ(n)+n2q=1∑nE[T(q−1)]=n2q=2∑n−1E[T(q)]+Θ(n)
- 基本思路是将前一半的 logk 放缩成 log(n/2) ,后一半的 logk 放缩成 logn 。
k=2∑n−1klogk=k=2∑⌈n/2⌉−1klogk+k=⌈n/2⌉∑n−1klogk≤log(n/2)k=2∑⌈n/2⌉−1k+log(n)k=⌈n/2⌉∑n−1k=lognk=2∑n−1k−k=2∑⌈n/2⌉−1k=2(2+n−1)(n−2)logn−2(2+⌈n/2⌉−1)(⌈n/2⌉−1−2)≤(2n2−2n−2)logn−(8n2−2n−23)=21n2logn−81n2−(logn−1)2n−2(logn−43)≤21n2logn−81n2
- 假设 E[T(n)]≤nlogn+Θ(n) ,代入第 3 问中的递归式,有
E[T(n)]=n2q=2∑n−1E[T(q)]+Θ(n)≤n2q=2∑n−1(qlogq+Θ(q))+Θ(n)=n2q=2∑n−1qlogq+Θ(n)≤n2(21n2logn−81n2)+Θ(n)=nlogn−41n+Θ(n)=nlogn+Θ(n)
于是, E[T(n)]=O(nlogn) 。