算法导论第7章习题
做题要猜出题目的意图。
7.1-1 略
7.1-2 r-1, add if q=r-1, then $q = \lfloor (p+r)/2 \rfloor$。这说明元素全都一样的时候,quicksort伤不起。worst-case了,要$\Theta(n^2)$了。快排弱点1.
7.1-3 pivot要比较所有的数组元素,自然是n
7.1-4 PARTITION中Line4的<= changed to >=
7.2-1 T(n)=T(n-1)+\Theta(n)=T(0)+n*\Theta(n)=\Theta(n^2)
7.2-2 同7.1-2
7.2-3 These exercises demonstrate the weakness of the original quicksort. It’s the worst case. T(n) = T(n-1)+\Theta(n)=\Theta(n^2)。快排弱点2.
7.2-4 银行喜欢时间排序,用户喜欢支票号排序。这道题又说明了在nearly sorted list中,插入排序beat了快排。快排弱点3.因为用户使用多张支票是连号的,所以在一个时间段内,顶多是这几张支票打乱了顺序。而不是一天24小时之内所有的支票全部乱序。所以这个序列是差不多有序的。如果n个用户每次使用k张支票,插入排序最多需要O(nk)时间,如果k=1,2很小的话,插入排序近似线性。但是快排则会产生最快也只有\Theta(nlgn)时间复杂度。所以当一小组数据量小,整个数据差不多有序的情况下,还是插入排序好。
7.2-5 这道是证明无论多差的划分,1-a:a,0<a<=0.5, 总会使得递归树\Theta(logn)。na^d=1, n(1-a)^d取对数即可。
7.2-6 这道加※题好像不难。在n个数据中比1-a:a的划分左右个一个:在pivot左边的元素数量:在pivot右边的元素数量=1-a:a或者a:1-a。比这种划分差的划分有n*2a个。所以得到1-a:a好的划分的概率有1-n*2a/n=1-2a。
7.3-1 对随机算法分析worst-case没有意义。随机化不会改变worst-case的时间复杂度,而是降低遇到worst-case的概率。
7.3-2 RANDOM-PARTITION是在在每次选PARTITION的pivot中调用一下RANDOM-GENERATOR。有多少次选pivot就有多少次随机数生成。pivot一旦被选出来后,就不会在加入到以后的sort和partition中了。所以选pivot顶多有n次。也是至少有n次的。如果不是,那么有一个元素没有被选为pivot。而元素的比较全部是在pivot与数组内的元素所比较,所以这个元素跟其他任何元素都没有比较过,因为不能确定它的次序,所以排序还没有完成。那么,无论在best-case还是worst-case里,都有\Theta(n)次调用。
7.4-1 要证明quicksort worst-case需要$\Omega(n^2)$时间。和书中关于7.4.1的证明是一样的,假设的部分变成T(n)>=cn^2,就差不多了。
7.4-2 这道题要证best-case要至少\Omega(nlgn)时间。这就T(n)右边的公式改成min。假设T(n)>=cnlgn.
T(n)>=cmin_{0<=q<=1}(qlgq + (n-q-1)lg(n-q-1))。min中的式子g(q)用求一阶和二阶导数的方法就最小值。当某一点一阶导数为0,二阶导数为正的时候,该点取到极小值,这里q=(n-1)/2。这个极值点肯定是唯一的,所以是最小值。把q代入再经过一些关于渐进符号的化简得到T(n)>=nlgn,对于某一个c来说。
7.4-3 q^2+(n-q-1)^2在q=0 – n-1 求最大值,高中的二次函数,略。
7.4-4 RANDOMIZED-QUICKSORT期望时间就相当于QUICKSORT的期望时间。而QUICKSORT的best-case是\Omega{nlgn}的,所以期望时间至少是\Omega{nlgn}的。
7.4-5 quicksort在递归到只有几个元素大小的数组时开始用插入排序的方法。改进的快速排序方法在期望时间=原始快排的期望时间+插入排序方法的期望时间。这里还是要用到7.4的分析方法。对于快排还要计算期望比较次数。因为被划分在同一个小数组k中的元素,是不会在快排里比较的。所以\(X_{ij}\)只要计算那些i和j相差k-1个以上的元素比较就可以了。
那么快排的期望时间是\(O(k^2)\),假设优化后的快排产生的小数组大小\(O(k)\),总共有\(O(n/k)\)个。那么把这些时间加起来就是\(O(nk+n\log(n/k))\)。
这道题没想到用\(X_{ij}\),还是看了别人的解答。对于算法的概率分析不给力啊。我觉得还要注意的是这个优化的快排划分出来的小数组大部分是小于k的。
7.4-6 用三分中值来做pivot得到的划分比a:1-a差的概率是\( \sum_{i=na}^{i=n-na} \frac{(j-1)(n-j)}{C^3_n} \)
Problems
7.1 Hoare划分的正确性
a. 略
b. 这里有个勘误,中文版也没有修正,自己看的英文版也没有修正。假设A[p,r]至少有两个元素。Hoare划分其实情况很复杂,看似很简单,但是你真要判断程序如何终止的还挺麻烦的。如果越界,那么必然发生在Line6和Line8。j递减至多停在A[p],这时候i至少>=p,所以i>=j,循环跳出,程序结束。Line8也不会越界。因为i第一个碰到的元素就是A[p],i=p。A[i]要么不交换要么交换。如果交换的话(因为至少有两个元素,自己月自己不交换),那儿必然在i<j交换,所以A[p]交换后跑到后面去了。i至多指向原来的A[p]。这时候至少j<=i了。所以跳出循环。如果不交换的话,因为Line6中的j一直减少到i>=j,j必然停在A[p],这时候i=j,所以循环跳出,i不在访问其余的元素了。
Line6,8一定要<=符号,虽然当所有元素全都一样的时候会把所有的东西都交换一遍,但是改成<的话,会造成越界的。
c. 因为不会越界访问所以, p<=j<=r。j有没有可能取到r?(这也是为什么勘误里要改成至少两个元素的原因了)。如果j=r,那么i=j=r,这时候A[j]=A[p]。也就是说第一次执行Line6后,Line6不再执行,那么while循环只执行了一次。那么必然Line8的i必然要增加2,可是i=p时候,a[i]=x,i就跳出repeat-until循环了,所以不能满足增加2的要求。所以j不可能=r。
d. Loop Invariance:循环开始之前是A[p,…,i]中的元素<=x,A[j,…,r]的元素>=x。
Initialization: i=p-1, j=r+1, 所以两个数组都是空的满足条件
Maintenance: 执行到Line8的时候,A[p,…,i-1]都是<=x,A[j+1,…,r]都是>=x的。A[i]>=x, A[j]<=x。如果i<j,把A[i]和A[j]交换。那么就保持Loop invariance.
Termination: i>=j,所以i和j之间没有元素。而且i>=j之后不会再交换了。所以当j被返回的时候,j左边有数组<=x,j右边有数组>=x。
e. 略
7.2 对快速排序算法的另外一种分析
a. 显然的都是1/n。
b. T(n)有n种情况,每种情况是n个元素的一个被选作pivot,每种情况所需要是\(T(n)=T(q-1)+T(n-q)+\Theta(n)\)。因为T(n)也是一个随机变量,用指示随机变量表示再去期望就是题目要求的公式。
c. 显而易见
d. 这里有勘误,k的下标从2开始
\[ \begin{aligned}
\sum_{k=2}^{n-1}k\lg k = \sum_{k=2}^{\lceil n/2 \rceil - 1}k \lg k + \sum_{i=\lceil n/2 \rceil}^{n-1}k \lg k & \le \\
\sum_{k=2}^{\lfloor n/2 \rfloor -1} k( \lg n –1 ) + \sum_{k=\lfloor n/2 \rfloor}^{n-1} k \lg n & \le \\
\sum_{k=1}^{n-1} k \lg n - \sum_{k=2}^{\lfloor n/2 \rfloor -1} k & \le \\
n^2/2 \lg n + n^2/8
\end{aligned}
\]
e. 利用d的结果,最终公式推导就很直接了。c中的式子乘上\(2/n\),略去\(O(n)\)项既得。
7.3 Stooge排序
a. 我觉得Stooge sort有点奇怪:Stooge sort最多递归到数组里只有2个元素。当有3个元素以上的时候才把数组划分成3份。但是交换元素在任何时候都会进行。但是只要在有两个元素的时候交换元素就可以了,而不必在其他情况下交换首尾两个元素。Python试了一下,也是可以的。
数学归纳法证明Stooge sort可以按照递增的方式排序数组A[i,…,j]。假设只有2个元素通过交换A[i]和A[j]可以保持元素的递增顺序。如果数组元素有n个,假设我们可以对有2n/3个元素可以按照递增的顺序排序。先把数组分成A,B,C三份。前k最大的元素记为集合X分布会落在A,B,C中的任何一个。\(k=\lfloor j-i+1 \rfloor\)所以B中的元素至少是n-2k>=k,说明B足以容纳前k个最大的元素。当Line6排序的时候,在A,B出现的X的元素必然少于等于k个,所以必然全部在B中,当Line7排序的时候,则全部的X元素都在B,C中了。X中的元素必然按顺序排满了A中最后k个位置,而且X中的元素都大于等于A中剩下的n-k个元素。其余的元素刚好被Line8排序完成。
b. T(n) = 2T(2n/3)+Θ(1) 得到\(\Theta(n^{2.7})\)
c. 插入排序、快速排序:Θ(n^2); 堆排序、合并排序:Θ(nlgn)。这个教授受伤了。
7.4 快速排序中的堆栈深度
a. 快排优化策略中的第2种,前面有用插入排序,后面有三数取中。当p=r时,已经正确排序了。对于有n个元素的数组,假设对于任何小于n个元素的数组都可以正确排序。因为Line3的Partition可以把数组分成q左边的比A[q]小的数组,和右边的比A[q]大的数组。因为左数组元素个数必小于n,调用quicksort’后可以正确排序。p=q+1后,quicksort’所要排序的数组A[q+1,…,r]元素也小于n,所以也能正确排序。左右两边都是有序的而且右边的任何一个元素大于等于左边的任何元素,所以整个数组都是有序的。
b. 这种悲催的情况只能发生在,partition划分出来的q=r,右数组为空,左数组比原数组大小只减少了1.那么Line4就要用n个递归了。
c. 只要把Line4-5改成先排序小数组就可以了,因为每次递归总会使得要排序的数组最多只为原来的一般,所以递归深度只有\( \lg n\)
7.5 “三数取中”划分
a. \(p_i=\frac{(i-1)(n-i)}{C_n^3}\)
b. 这几道题都需要微积分的知识了。下面的题目有Gatech教授的solutions答案。由a.公式可知道选取中位数的概率等于\(\frac{(n-1)^2}{2C_n^3}\)。原始的概率是\(1/n\)。那么两个相比对n取极限得到
\[\lim_{n \to \infty} \frac{\frac{(n-1)^2}{2C_n^3}}{n} = \frac{3}{2} \]
c. 提示是利用积分近似合适。学过微积分的知道黎曼积分可以通过达布和来定义。如果要计\(x^2\)函数下从0到1的面积。那么
\[ \int_0^1 x^2 dx = \lim_{n \to \infty} \sum_{i=1}^n \frac{1}{n}(\frac{i}{n})^2\]
这里也是一样
\[ \lim_{n \to \infty }\sum_{i=\frac{n}{3}}^{\frac{2n}{3}} \frac{6(i-1)(n-i)}{n(n-1)(n-2)}\]
n足够大时,n约等于n-1约等于n-2.
\[ \lim_{n \to \infty } \frac{1}{n}\sum_{i=\frac{n}{3}}^{\frac{2n}{3}} \frac{6(i-1)(n-i)}{n^2} = \int_{\frac{1}{3}}^{\frac{2}{3}} 6x(1-x) dx = \frac{13}{27} \]
这个值要比原始快排方法选取好划分的概率1/3提高了。
d. 因为三数取中还是要将数组分为两半,每一半都需要递归得排序。只是提高了碰到小递归树的概率。每棵递归树的高度都还是\(\Theta(\lg n)\),每层要比较的次数也都是\(\Theta(n)\).
7.6 对区间的模糊排序
a. 因为当有两个区间重叠时比如[1,3],[2,4]重叠为[2,3],即使重叠区域只有1个元素,我们都可以选取这个元素作为c_i和c_j(i<j),使得c_i<=c_j。所以对于重叠的区间是可以看做等于关系的。而对于没有重叠区域的区间来讲,[0,1],[2,3]那必须使得[0,1]在前,[2,3]在后,先从前者取c_i,后者取c_j(i<j),才能使得c_i<=c_j。这样不重叠的顺序可以通过判断一个区间[a,b]的b是不是大于另外一个区间的a,对于这样的区间看做小于关系。把这个比较关系带入到快排中的PARTITION中的比较两元素的判断语句中就可以了。
b. 因为区间的关系同数的关系是一样的,算法还是讲数组一分为二,两边分别递归调用,和快排的期望时间一样都是\(\Theta(n \lg n)\)。当所有区域都重叠时,我们可以把PARTITION稍微修改一下,变成划分成3个区域,小于,等于,大于pivot的3个区间的数组。对于最左和最右两个集合再作递归。这样当全部元素都是一样的时候,pivot只是不停地把相等的区间放入等于这个集合。扫描所有区间自然要\(\Theta(n)\)时间复杂度。