快速排序相关C代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 void swap(int* A,int i,int j) 5 { 6 int temp=A[i]; 7 A[i]=A[j]; 8 A[j]=temp; 9 } 10 int Partition(int* A,int p,int r) 11 { 12 int x=A[r-1]; 13 int i=p-1,j=p; 14 for(;j<r-1;++j) { 15 if(A[j]<=x) 16 { 17 swap(A,++i,j); 18 } 19 } 20 swap(A,++i,r-1); 21 return i; 22 } 23 int Random_Partition(int* A,int p,int r) 24 { 25 srand((unsigned int)time(NULL)); 26 int i=rand()%r; 27 swap(A,i,r-1); 28 return Partition(A,p,r);
29 } 30 int Original_Partition(int* A,int p,int r) 31 { 32 int x=A[p]; 33 int i=p-1,j=r; 34 while(1) 35 { 36 while(A[--j]>x); 37 while(A[++i]<x); 38 if(i<j) 39 swap(A,i,j); 40 else 41 return j; 42 } 43 } 44 void Quick_Sort(int* A,int p,int r) 45 { 46 if(p<r-1) 47 { 48 int q=Partition(A,p,r); 49 //对于Hoare版本,注意到主元并未独立出来 50 //int q=Original_Partition(A,p,r); 51 //Quick_Sort(A,p,q+1); 52 Quick_Sort(A,p,q); 53 Quick_Sort(A,q+1,r); 54 } 55 } 56 int main() 57 { 58 int A[30]; 59 int i=0; 60 printf("Original:\n"); 61 for (;i<30;i++) 62 { 63 A[i]=rand()%10; 64 printf("%d\t",A[i]); 65 } 66 Quick_Sort(A,0,30); 67 printf("\nRearrange:\n"); 68 for(i=0;i<30;++i) 69 { 70 printf("%d\t",A[i]); 71 } 72 printf("\n"); 73 }
约定起始下标是0,终止的下标是末尾后一个(类似C++的迭代器)。这样就与书中1-n有差别(这里是0-n,n不可达)。上一章也是这么写的,这里做一下注释。
本章大部分篇幅用于分析快排的性能问题,使用指示器随机变量的版本比原来的容易理解多了…【MIT的公开课用的仍然是first edition的,木有引入这个概念】。
exercises:
7.1-2 q=r;修改Partition使得q=$\lfloor (p+r)/2 \rfloor$的方法其实很多,因为这里没有限制不能显式检查这种情况…不过最好的方法还是使用Hoare原版的划分方法(见思考题7-1)。
7.1-3 line4到line6是常数级消耗,即Θ(1),那么主要消耗在line3的循环上,为Θ(n-1)所以总的运行时间为Θ(n)。
7.1-4 将line4的判断符号由≤改为≥即可。
7.2-1 \[ \begin{split}T(n) &= T(n-1)+\Theta(n) \\ &= \Theta(n-1)^2+\Theta(n)\\ &\le c(n-1)^2+dn\\ &\le cn^2 = \Theta(n^2) \end{split} \]
7.2-2,7.2-3 都是相同值和已经排好序的情况类似,都是最坏情况,前者要进行n次自交换,后者划分一定是不均匀的,两种情况最后返回的j值都在数组尾部。这样需要进行的划分次数必须是n,那么运行时间就是Θ(n2)。
7.2-4 在几乎已经排好序的序列上进行排序,这时快排接近7.2-3中的最坏情况;而对于插入排序,其内循环中确定位置需要遍历元素的个数大幅度减少,使得整个算法的时间趋近于Θ(n),也就是liner time。
7.2-5 递归树的表达式为T(n)=T(αn)+T((1-α)n]+Θ(n),由于alpha≤1/2,那么递归树最后到达叶节点的分支表达式必定是(1-α)h*n=1,解得h=-lgn/lg(1-α);
而最先到达叶子结点的高度h必定满足αh*n=1,也可以解出题设给出的答案。
7.2-6 可以通过作图简单得证,将长度为1的线段划为1-α和α两部分(α≤1/2),显然更均匀的划分比定位于α与1/2之间,或者与其以1/2为轴对称的区间内,所以总的长度为2×(0.5-α)即1-2α。
7.3-1 分析最坏情况毫无意义,因为随机化的最坏情况与不随机的最坏情况是一致的。
7.3-2 随机数发生器位于Random-Partition中,所以最坏情况下调用Θ(n)次,最佳情况Θ(lgn)次。
7.4-1 代换法,注意
\[ \max_{0 \le q \le n-1}(T(q)+T(n-q-1)) \ge \frac{(n-1)^2}{2} \]
这里用了代数上的不等式知识。
7.4-2 最佳情况即T(n)=2T(n/2)+Θ(n),但是严谨的证明方式是使用类似7.4-1中的递归式,然后使用不等式和代换法证明该下界。
7.4-3 这里可以用对应的连续函数的导数来证明单调性,然后求出最大值所在的点。
7.4-4 正文中已经证明了…
7.4-5 如果需要在小数组上实现插入排序,只要修改
Quick-Sort(A,p,r)
if r-p+1≥k
then ...
使用Sort调用Quick-Sort,然后再调用Insertion-Sort即可。
期望运行时间的证明比较麻烦,对于Insertion-Sort部分,由于未排序的片段长度小于k,所以内循环的数据移动长度必定<k,即整体的运行时间<Θ(nk)
下面需要证明插排部分的期望运行时间≤Θ(nlg(n/k)),使用类似正文中统计总partition次数的方法,最后
\[ E[x]=\Sigma_{i=1}^{n-k-1}\Sigma_{m=k}^{n-i}\frac{2}{m+1} < \Sigma_{i=1}^{n-k-1} (lgn-lgk) \le nlg(n/k) \]
总结以上,得证(但是这里的证明似乎有些问题,主要考虑到n是1到∞,但是k肯定是小于n的,直接使用收敛和公式得出最后的结果恐怕不妥…)
实际使用中,k值的确定方法很简单:只要在任意k长度的小数组上插排的速度正好等于或者快于快排,那么就取这个临界值作为k。
7.4-6 这题的意思总结一下就是:如果实行“三位取中”的策略,如何能保证最后这个中位数左边正好有αn-1个数,右边正好有(1-α)n个数【或者正好相反】,那么
\[ Pr=2 \frac{(\alpha n-1)(n-\alpha n)+\alpha n(n-\alpha n-1)}{\binom{3}{n}} \]
经过化简计算,结果应该是$\frac{2\alpha n-2 {\alpha}^2 n-1}{n^2-3n+2}$
以上结果建立在所有元素都不相等的情况下…
Problems:
7-1 这里就是Hoare最初提出的Quick-Sort的伪代码了,在很多值相等时,这种写法的效率更高(当然平均效率还是nlgn)。
这里比较难以理解的地方是每轮划分后主元的位置并未独立出来,而是仍然被放入下一轮继续排列。所以Quick-Sort的代码有些许对应的微调。
循环不变式见本题b-d,主要思想就是[p,j]的值≤[j+1,r]的值。
7-2 不引入指示器随机变量的分析版本,计算比较复杂,不过该证明的可扩展性可能更好吧。俺数学不行,就略过这题了。
7-3 一个蛋疼的排序法,T(n)=3T(2n/3)+Θ(1),由主方法可得复杂度为Θ(n2.7)左右【lg3/lg1.5】。
7-4 将尾部递归改为迭代,以减少对堆栈的使用。
Quick-Sort'(A,p,r)
while p<r
do $\vartriangleright$Partition and sort left subarray
q←Partition(A,p,r)
Quick-Sort'(A,p,q-1)
p←q+1
b> 仍然是最坏情况划分会导致堆栈深度为Θ(n).
c>先排序长度较小的一边,这样递归树会以≤Θ(lgn)的深度到达叶子结点,即
Quick-Sort''(A,p,r)
while p<r
do $\vartriangleright$Partition and sort left subarray
q←Partition(A,p,r)
if q-p≥r-q
then Quick-Sort''(A,q+1,r)
r←q-1
else
Quick-Sort''(A,p,q-1)
p←q+1
7-5 概率分析问题,参加7.4-6
7-6 这里只要明确如何判定两个区间的关系就很容易写出算法。
设定区间$[a_i,b_i]=i$和$[a_j,b_j]=j$,则
if $a_j>b_i$,i<j
if $a_i>b_j$,i>j
其他情况下二者相等【换言之:只要两者有重合区间就认为两区间相等】
以这个判定方法使用快速排序(原版)含有该区间的数组(结构数组)进行排序即可。