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的逆序对数目。

posted @ 2012-08-15 16:45  生无所息  阅读(300)  评论(0编辑  收藏  举报