CLRS2e读书笔记—Chapter1-2
凛冬将至。——《冰与火之歌》
————————————————————————————
插入排序、合并排序:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <malloc.h> 4 #include <string.h> 5 void insertion_sort(int* array,int p,int q) 6 { 7 int j=p+1,i; 8 for(;j<q;++j){ 9 int key=array[j]; 10 for(i=j-1;i>=0;--i){ 11 if(array[i]>key) 12 array[i+1]=array[i]; 13 else{ 14 array[i+1]=key; 15 break; 16 } 17 } 18 } 19 } 20 void merge(int* array,int p,int q,int r) 21 { 22 int n1=q-p; 23 int n2=r-q; 24 int *L=(int*)malloc(sizeof(int)*n1); 25 int *R=(int*)malloc(sizeof(int)*n2); 26 if(!L || !R)exit(EXIT_FAILURE); 27 memcpy(L,array+p,sizeof(int)*n1); 28 memcpy(R,array+q,sizeof(int)*n2); 29 30 int i=0,j=0,k=p; 31 while(i<n1 && j<n2) 32 { 33 if(L[i]<=R[j]) array[k++]=L[i++]; 34 else array[k++]=R[j++]; 35 } 36 while(i<n1)array[k++]=L[i++]; 37 while(j<n2)array[k++]=R[j++]; 38 free(L); 39 free(R); 40 } 41 void merge_sort(int* array,int p,int r) 42 { 43 if(p<r-1){ 44 int q=(r+p)/2; 45 merge_sort(array,p,q); 46 merge_sort(array,q,r); 47 merge(array,p,q,r); 48 } 49 } 50 int main() 51 { 52 int A[30]; 53 int i=0; 54 printf("Original:\n"); 55 for (;i<30;i++) 56 { 57 A[i]=rand(); 58 printf("%d\t",A[i]); 59 } 60 //insertion_sort(A,0,30); 61 //C中下标与范围并不一致,所以这里约定 62 //第二个参数是不可达的 63 merge_sort(A,0,30); 64 printf("\nRearrange:\n"); 65 for(i=0;i<30;++i) 66 { 67 printf("%d\t",A[i]); 68 } 69 printf("\n"); 70 }
exercises:
2.1-4
Binary_Add(A,B,C)
flag<-0
for i<-1 to n
do C[i]<-A[i]+B[i]+flag
flag<-C[i]/2
C[i]<-C[i] mod 2
if flag=1 then
C[n+1]<-1
2.2-2 选择排序
Selection-Sort(A)
for i<-1 to n-1
do minus<-A[i] and key <- i
for j<- i+1 to n
do if A[j] < minus then
minus<-A[j]
key <- j
A[i]<->A[j]
算法已经选出前n-1个最小的元素,那么第n个必然是最大的元素。最佳情况,已经排序好,显然是线性的$\Theta(n)$,最坏情况正好相反,为$\Theta(n^2)=\Theta(n-1+n-2+...+1)$
2.3-4 将插入排序改成递归结构
Insertion-Sort'(A,n)
if n>1
then Insertion-Sort'(A,n-1)
Insertion(A,n-1,A[n])
Insertion(B,size,elem)
i <- size
while i>0 and B[i]>elem
do B[i+1] <- B[i]
i <- i-1
B[i+1] <- elem
显然递归表达式为:
\[
T(n)=\begin{cases}
\Theta(1)\quad& \text{if $n=1$}\\
T(n-1)+\Theta(n)\quad& \text{if $n>1$}
\end{cases}
\]
可知最坏情况渐近分析时间为 $\Theta(n^2)$
2.3-5 二分查找
递归版本:
Binary-Search(A,p,r,a)
if r>p then
q<-(p+r)/2
if A[q]>a
then Binary-Search(A,p,q-1,a)
else if A[q]<a
then Binary-Search(A,q+1,r,a)
else return q
else if A[p]=a then return p
else return nill
迭代版本:
Binary-Search'(A,p,r,a)
while r $\ge$ p
do if r=p
then if A[p]=a
then return p
else return nill
else
do q<-(p+r)/2
if A[q]>a
then r <- q-1
else if A[q]<a
then p<-q+1
else return q
递归式很明显是$T(n)=2T(n/2)+\Theta(1)$,因此worst run-time 是$\Theta(lgn)$
2.3-6 这里的意思就是将Insertion-Sort的第5-7行的线性查找插入位置的代码修改成通过二分查找来找位置。由2.3-5可知二分查找的时间复杂度为\Theta(lgn),那么修改后的Insertion-Sort的复杂度应该是lg1+lg2+lg3+...lg(n-1)=lg$\prod_{i=1}^{n-1}i$=lg((n-1)!)=$\Theta(nlgn)$
但是很遗憾,这只是查找的复杂度,采用数组这种数据结构,在找到位置后必须将该位置右端的元素后移一位,这个时间计算在内,最后的复杂度仍然是$\Theta(n^2)$;如果采用链表的数据结构,就不需移动数据,而只用常数时间改变指针指向,这样复杂度就降低到$\Theta(nlgn)$.
2.3-7 由于二分查找的时间为\Theta(lgn),因此这里只需对每个A[i]二分查找S-A[i]即可(需要先排序)。
Problems:
2-1 在合并排序中嵌入插入排序,改善小数组的排序性能。
a>n/k*$\Theta(k^2)=\Theta(nk)$
b>需要合并n/k个长度为k的子序列,先合并成2n/k个长度为2k的子序列,然后以此类推直到合成一个完整的序列,每层树的代价是n/k*$\Theta(2k)=\Theta(n)$
树的深度h满足:$k·2^h=n$,解得h=lg(n/k),因此总的代价为$\Theta(nlg(n/k))$
c>即计算 $\Theta(nk+nlg(n/k))$=$\Theta(nlgn)$成立的k的表达式,由于$\Theta$是弱表达式符号,显然只需k=$\Theta(lgn)$就能使等式左边舍去加号右边的项,进而使二者相等。至于为什么它是最大渐进表达式,因为显然比这个表达式更大就不能使等式成立,但是如果令k=1,显然等式也是成立的。
d>在实际中,k值取恰好能够使快速排序代价小于合并排序的输入规模。
2-2 冒泡排序
Bubble-Sort
for i<- 1 to length(A)
do for j<- length(A) down to i+1
do if A[j] < A[j-1]
then A[j] <->A[j-1]
显然冒泡排序的性能和插入排序差不多…这题主要考察循环不变式的证明。
循环不变式的证明通常需要需要三步:初始化(即第一次迭代之前),保持(在迭代的过程中)和终止(迭代结束后),在这个过程中,必须证明循环不变式保持正确。循环不变式的关键在于观察循环过程中对不变量的累积。
2-3 霍纳规则
a>循环终止条件为i>=0,循环体只是不停的迭代,复杂度为$\Theta(1)$,所以渐进运行的时间是$\Theta(n)$
b>朴素多项式求值,也就是常规的计算方式,将P(x)按常规方法展开:P(x)=$a_0+a_1x+a_2x^2+\cdots+a_nx^n,代码一目了然
Naive-PE(a,x)
y<-0
z<-1
for i<-0 to n
do y<- y+$a_i$ * z
z<-z*x
显然这里的算法复杂度仍然是$\Theta(n)$,但是常数因子比逆序递归求解要大。
2-4 逆序对
b>显然完全逆序排列的数组拥有最多的逆序对,个数为(n-1)+(n-2)+...+1=n(n-1)/2对
c>插入排序在数组几乎已经排好序的情况下性能最好,所以逆序对越多显然运行时间越长
d>求逆序对的数目,按着提示修改合并排序,在合并时如果L[i]>R[j],就对逆序对个数进行统计。
分析:对于每一个L[i]>R[j],L[i],L[i+1]...L[n1]与R[j],R[j+1]...R[n2]都组成逆序对,但是如果两边都统计的话,最后的结果会有重复叠加。
注意到L中元素任意调换顺序不影响L中元素将R[j]构成逆序对的数目,而总的逆序对数目=L中逆序对的数目+R中的逆序对数目+L对R的逆序对数目。
而L中逆序对的数目=L中左半边逆序对的数目+L中右半边逆序对的数目+二者相对的数目。这也是一个递归的公式,当递归达到边界时,L与R的元素都只有一个,不存在逆序对,所以所有逆序对的数目=L对R逆序对数目的和,因此对每个L[i]>R[j]有n1-i+1个逆序对,当R[j]的位置确定后,再累加R[j+1]对L的逆序对数目。