算法导论读书笔记2

第2章算法入门

浮于表面不如深入其中,送给自己,自己是最大的敌人,那么就尽最大努力去克服自己,沉思,冷静,不浮躁!

 

勘误:在算法导论第9页,扼要的扼

内容提要:

(1)伪代码的表示方法

(2)插入排序算法分析

(3)循环不变式

(4)算法设计之分治法(divide-and-conquer)

(5)合并排序算法分析

 

1.伪代码的表示方法

①伪代码与流程的编程语言类似;

②伪代码可以用英语表示,当然中文也可以;

③不关心其他细节,比如异常处理等软件工程需要关注的问题

约定:①必要的缩进;②控制语句:while、for、repet、if、then、else等等;③注释表示有点特征哈;④多重赋值;⑤变量的定义是不需要的,直接用即可;⑥数组的访问同C;⑦参数值传递;⑧布尔运算的短路能力;⑨空指针为NIL等等等等

2.插入排序算法分析

输入:n个数,可以表示为有序列<a1,a2,…an>;

输出:同样n个数,只是顺序变了变为<a1’,a2’,…,an’>,其中a1’<=a2’<=…<=an’或者相反的结果。

另一个术语:这些待排序的数字也叫关键字即key

实例:数组A={5,2,4,6,1,3}

分析使用插入排序过程:

第1步:5,2,4,5,1,3

该步骤在第1个元素插入的情况下准备开始插第2个元素2

此时,从2之前的一个元素开始搜索,因为前面的元素已经有序了,那么我们只要从2之前的元素倒序的方式开始找比2小且第一次出现的数字,然后将2插入到该数字之后,那么当前情况下我们往前找,结果发现5比2大,则继续往前搜索,发现前途一片空白,所以直接将2插入到空白之后,即5之前

结果:2,5,4,5,1,3

第2步:2,5,4,5,1,3

该步骤需要插入元素4,我们从4之前的一个元素5从5开始往前搜,第一次比较发现4比5小,继续往前搜,发现4比2大则把4插入到2之后。

第3步:2,4,5,5,1,3

该步骤需要插入元素5,我们从5之前的一个元素开始搜,第一次为5,发现不大于5(实际上是和5相等),则将5插入到5之后。

第4步:2,4,5,5,1,3

该步骤需要插入元素1,我们从1之前的一个元素开始搜,第一次为5,pass,第二次为5,pass,第三次为4,pass,第四次为2还是pass,继续往前走,发现前面没人了,咋办,直接插入空白之后。

第5步1,2,4,5,5,3

好了,终于到最后一步了,大松一口气哈。

此次需要插入元素3,从3之前的一个元素开始搜索,第一次为5,过,第二次为5,继续过,第三次为4,过,第四次为2,发现不大于3(即小于3了),那么咋办?插入到2之后呗。

好了该实例终于分析完毕了,我想现在对于插入排序的排序过程应该有了很深刻的了解了吧

小结:

插入排序的实质就是,从第2个元素开始插入,然后一个劲地以倒序的方式从前面的元素中搜索不大于待插入的元素的元素(包括小于和等于啦),一旦找到,则将元素插入该元素之后,这样不断重复地插入就完成了插入排序的过程。

写了这么多,下面给出插入排序的代码:

代码:

void insertSort( int* A, int n );

int main(intargc, char* argv[])

{

     int A[6]={5,2,4,5,1,3};

    

     printf("插入排序前:\n");

     for( int i = 0; i <6; i++ )

     {

         printf("%d\t",A[i]);

     }

     printf("\n");

 

     insertSort(A,6);

     printf("插入排序后:\n");

     for( int i = 0; i <6; i++ )

     {

         printf("%d\t",A[i]);

     }

     printf("\n");

 

     return 0;

}

 

void insertSort( int* A, int n )

{

     int key = 0;

     int i = 0;

     int j = 0;

 

     for ( i = 1; i < n; i++ )

     {

         //待插入元素赋值给key

         key = A[i];

 

         //从待插入的元素之前倒序进行搜索

         j = i - 1;

         //终止条件为直至第一个元素,或者找到的元素不大于待插入元素

         while ( j >= 0&& A[j]>key )

         {

              //边查找不大于key的元素边挪元素到其后一格

              A[j+1]=A[j];

              j--;

         }

        

         //将待插元素插入到所找到的不大于待插元素之后

          A[j+1]=key;

 

 

     }

}

运行结果:

 

3.关于循环不变式

初始化:在第一轮迭代之前它是正确的

保持:在循环的某一次迭代开始之前它是正确的,在下一次迭代开始之前它也是正确的

终止:循环结束后,不变式给了我们一个有用的性质,那就是所得的最终序列是有序的,按升序或者降序排列。

通俗一点讲:

初始化:就是在第一次迭代之前已经有一个元素,因为只有一个元素,我们可以称之为降序的(或者升序)

保持:在某一次排序的过程前,待插入的元素之前所有的元素都是降序的(或者升序),那么插入该元素之后这些元素还是降序的(或者升序)

终止:在排序结束之后,由以上两点,可以知道所有的序列都是按照降序(或者升序)排列了

 

练习答案:

2.1-1以图2-2为模型,说明INSERT_SORT在数组A=<31,41,59,26,41,58>上执行过程。

第1步:31,41,59,26,41,58

待插元素为41,此时从41之前的元素倒序搜索,发现31不大于41,则插入到31之前

第2步:31,41,59,26,41,58

待插元素为59,此时从59之前的元素倒序搜索,第一次发现41不大于59,则插入到41之后

第3步:31,41,59,26,41,58

待插元素为26,此时从26之前的元素倒序搜索,第一次发现59大于26,继续往前搜索,第二次发现41大于26,继续往前搜索发现31大于26,继续往前搜索,发现前途一片空白,直接插入。

第4步:26,31,41,59,41,58

待插元素为41,此时从41前一个元素倒序搜索,第一次发现59大于41,继续搜索,第二次发现41不大于41,则此时直接将41插入该元素41之后。

第5步:26,31,41,41,59,58

待插元素为58,此时从58   前一个元素倒序搜索,第一次发现59大于58,继续搜索,第二次发现41不不大于58,则停止搜索,将待插元素58插入到元素41之后。

 

2.2-2重写INSERTION-SORT,使之按非升序(而不是按非降序)排序。

//降序

void insertSort2( int* A, int n )

{

     int i = 0;

     int j = 0;

     int key = 0;

 

     for ( i = 1; i < n; i++ )

     {

         key = A[i];

         j = i - 1;

 

         while( j >= 0&& A[j]<key )

         {

              A[j+1] =A[j];

              j--;

         }

 

         A[j+1]=key;

     }

}

 

2.1-3考虑下面的查找问题

输入:一列数A=<a1,a2,…,an>和一个值v。

输出:下标i,使得v=A[i],或者当v不在A中出现为NIL

写出针对这个问题的线性查找的伪代码,它顺序地扫描整个序列以查找v,利用循环不变式证明算法正确性,确保所给出的循环不变式满足三个必要的性质。

伪代码:

SEARCH(A,v)

    For i=1 to length(A)

        If( A[i] == v )

            Return i

    Return NIL

证明:

初始化:在迭代之前,没有元素能够等于v

保持:在每一轮循环都没有找到等于v的元素

终止:当找到v之前,前面都有等于v的元素,直至找到v,算法停止

我不知道我的解答对不对-_-!

 

2.2-4有两个各存放在数组A和B中的n位2进制整数,考虑他们相加的问题。两个整数的和以二进制形式存放在具有n+1个元素的数组C中,请给出这个问题的形式化描述,并写出伪代码

输入:n位2进制整数A和B

输出:A+B的结果

代码:

void binAdd( int*A,int* B, int* C, intn );

int _tmain(intargc, _TCHAR*argv[])

{

     int A[7]={1,1,0,1,0,1,1};

     int B[7]={1,1,0,1,1,1,1};

     int C[8]={0};

    

    

     printf("A为:");

     for ( int i= 0; i < 7;i++ )

     {

         printf( "%d",A[i] );

     }

 

     printf("\nB为:");

     for ( int i = 0; i <7; i++ )

     {

         printf("%d",B[i]);

     }

     binAdd(A,B,C,7);

     printf("\n结果:");

     for (inti=0; i< 8;i++)

     {

         printf( "%d",C[i] );

     }

     return 0;

}

 

 

void binAdd( int*A,int* B, int* C, intn )

{

     int i = n - 1;

     //从最低位开始相加,也就是数组的最高位

     for ( ; i > 0; i-- )

     {

         //判断是否大于等于,需要进位

         if ( A[i] + B[i] >= 2 )

         {

              C[i+1] =A[i] + B[i] - 2;

              //若需要进位,直接放到原数组中去计算

              A[i-1] += 1;

         }

         else

         {

              C[i+1] =A[i] + B[i];

         }

     }

     //最后一步,因为C比A和B都多出一位,所以直接将结果放置C[1],并将进位放置C[0]

     if ( A[0] + B[0] >= 2 )

     {

         C[1] = A[0]+B[0]-2;

         C[0] = 1;

        

     }

     else

     {

         C[1]= A[0]+B[0];

         C[0] = 0;

     }

}

运行结果:


我的算法比较笨啦,呵呵,智商不高,只能靠勤奋了。

算法分析:

所谓算法分析实质上就是指对一个算法所需要的资源进行预测。

算法分析和所使用的内存模型以及CPU模型有关系

一般为单处理器+RAM(即随机存储器)

最佳情况、最坏情况、平均情况分析

随机化算法等等

练习

2.2-1用O形式表示函数n^3/1000-100n^2-100n+3

表示为O(n^3)

 

2.2-2考虑对数组A中的n个数进行排序的问题:首先找出A中最小元素,并将其与A[1]中的元素对换。接着找出A中次最小的元素,并将其与A[2]中的元素进行交换。对A中头n-1个元素继续这一过程。写出算法的伪代码,该算法称为选择排序。对这个算法来说,循环不变式是什么?为什么它仅需要在头n-1个元素上运行,而不是所有n个元素上运行?以O形式写出选择排序的最佳和最坏情况下的运行时间。

代码:

 

void selectSort( int* A, int n );

void selectSort2( int* A, int n );

int _tmain(intargc, _TCHAR*argv[])

{

     int A[9]={1,3,1,3,45,12,21,1,12};

    

 

     printf("原来:");

     for ( int i=0; i<9;i++ )

     {

         printf("%d\t",A[i]);

     }

     printf("\n降序");

     selectSort(A,9);

     for ( int i=0; i<9;i++ )

     {

         printf("%d\t",A[i]);

     }

     printf("\n升序:");

     selectSort2(A,9);

     for ( int i=0; i<9;i++ )

     {

         printf("%d\t",A[i]);

     }

     printf("\n");

     return 0;

}

 

//降序

void selectSort( int* A, int n )

{

     int i = 0, j = 0;

     int temp = 0, maxe = 0;

     for ( i = 1; i < n - 1; i++ )

     {

        

         //从第三个元素开始找最小的值

         j = i + 1;

        

         //假设第二个元素为最大元素

         maxe = i;

 

         //寻找剩下元素的最大值

         while ( j < n )

         {

              if ( A[j] > A[maxe] )

              {

                   maxe = j;

              }

              j++;

         }

 

         //交换

         temp = A[i-1];

         A[i-1] =A[maxe];

         A[maxe] =temp;

 

     }

}

 

//升序

void selectSort2( int* A, int n )

{

     int i = 0, j = 0;

     int temp = 0, mine = 0;

     for ( i = 1; i < n - 1; i++ )

     {

 

         //从第三个元素开始找最小的值

         j = i + 1;

 

         //假设第二个元素为最大元素

         mine = i;

 

         //寻找剩下元素的最大值

         while ( j < n )

         {

              if ( A[j] < A[mine] )

              {

                   mine = j;

              }

              j++;

         }

 

         //交换

         temp = A[i-1];

         A[i-1] =A[mine];

         A[mine] =temp;

 

     }

}

运行结果:


累了,就休息会,背背单词看看其他的,过会写。

昨天下午打了好大一会酱油,晚上帮同学写了个杨辉三角的面试题和一个xml递归读取的面试题。

这里我给出任意行的杨辉三角的代码

#include <memory.h>

void yanghui( int line );

int _tmain(intargc, _TCHAR*argv[])

{

     int line = 0;

     printf("请输入杨辉三角行数:");

     scanf("%d",&line);

     yanghui(line);

     scanf("%d",&line);

     return 0;

}

void yanghui( int line )

{

     int j = 0;

     int* cur = new int[line];

     int* pre = new int[line];

     memset( cur,0,line*sizeof(int) );

     memset( pre,0,line*sizeof(int) );

 

     for ( int i = 0; i < line; i++  )

     {

         j = i;

         if ( i == 0 )

         {

              for ( int c = 0; c < line; c++ )

              {

                   printf(" ");

              }

              printf("1\n");

         }

         else if( i == 1)

         {

              for ( int c = 0; c < line - 1; c++ )

              {

                   printf(" ");

              }

              printf("11\n");

              pre[0] = pre[1]= 1;

         }

         else

         {

              cur[0] = cur[j] = 1;

              for ( int k = 1; k < j; k++ )

              {

                   cur[k] =pre[k-1] + pre[k];

              }

              for ( int c = 0; c < line - j; c++ )

              {

                   printf(" ");

              }

              for ( int k = 0; k <= j; k++ )

              {

                   printf( "%d",cur[k]);

                   pre[k] =cur[k];

                   cur[k] = 0;

              }

 

              printf("\n");

 

         }

     }

 

 

 

     delete []pre;

     delete []cur;

 

}

运行结果:

算法导论读书笔记2_第1张图片

循环不变式:

初始化:在初始情况下,之前的元素都有有序的(或者为升序或者为降序)

保持:在使用选择排序之前,之前的元素都是有序的,在选择排序之后加入新的元素后,还是有序的

终止:在最后一个元素的时候,整个序列有序,因为基于初始化和保持的条件可以知道整个序列有序。

选择排序的最佳时间:O(n^2)

最坏情况:O(n^2)

我不知道是否对,但是我觉得,寻找最大值或者最小值是需要遍历的,另外外循环也是,所以最佳和最坏的都一样,都需要通过遍历来获得每次的最大值和最小值,根本没办法避免,也不存在最佳情况。因为你不知道你要找的最大或者最小元素究竟是哪一个,所以你只能遍历到最后才能确定你那个元素是最大的或者最小的。

 

2.2-3再次考虑线性查找问题,在平均情况下,需要检查输入序列中的多少个元素?假定待查找的元素是数组中任何一个元素的可能性相等,在最坏情况下又怎样呢?用O形式表示,线性查找最坏情况运行时间是多少?并说明

平均情况下需要查找序列中(1+n)/2个元素

最坏情况下:n个元素

即O(n)为最坏

 

2.2-4应如何修改任何一个算法,才能使之具有较好的最佳情况运行时间

根据算法的最佳情况改变输入数据的分布(比如说顺序),使之符合最佳情况条件,这样就能拥有最佳运行时间。

 

 

 

 

4.算法设计之分治法

算法设计之设计策略:分治法Divide And Conquer

分治策略:将原问题划分为n个规模较小结构与原问题相似的子问题递归地解决这些问题,然后再合并结果,就得到原问题的解。

 

分治法之合并排序:

 

先来看看合并是什么实现的。

实例:

下面从一个实例来分析合并的过程。

以升序为例:

待合并的数据

A<1,12,13,16>

B<11,12,13,15,17>

合并之后存放在C中

第一步:从A<1,12,13,16>中取出1,从B<11,12,13,15,17>中取出11

比较1和11,发现1比11小,此时将1存放到C中,那么C<1>

 

第二步:从A<1,12,13,16>中取出12,刚才从B中取的11并没有放进C

比较12和11,发现11小于12,此时将11存放到C中,那么C<1,11>

 

第二步:刚才从A中取的12没有放进C,从B<11,12,13,15,17>中取出12

比较12和12,发现12等于12,此时将A中的12存放到C中,那么C<1,11,12>

 

第三步:从A<1,12,13,16>中取出13刚才从B中的12没有存进C

比较13和12,发现12小于13,此时将12存进C,那么C <1,11,12,12>

 

第四部:刚才从A中取出的13没有放进C,从 B<11,12,13,15,17>中取出13

比较13和13,发现13等于13,此时将A中的13存放到C中,那么C<1,11,12,12,13>

 

第五步:从A<1,12,13,16>中取出16,刚才从B中取出的13没有放进C

比较16和13,发现13小于16,此时将13放进C,那么C<1,11,12,12,13,13>

 

第五步:刚才从A中取出的16并没有存进C,从B<11,12,13,15,17>中取出15

比较15和16,发现15比16小,此时将15存进C,那么C<1,11,12,12,13,13,15>

 

第六步:刚才从A中取出的16并没有存进C,从从B<11,12,13,15,17>中取出17

比较16和17,发现16比17小,此时将16存放到C,那么C<1,11,12,12,13,13,15,16>

 

第七步:A中无元素了,从B中把剩余的元素放进C即可

那么C<1,11,12,12,13,13,15,16,17>

 

OK,大功告成,合并成功。

 

 

下面给出代码:

 

//尽心,静心,精致,深入!

void merge( int* A, int p, int q, int r );

void merge2( int* A, int p, int q, int r );

int _tmain(int argc, _TCHAR* argv[])

{

     int A[11] ={1,2,1,12,13,16,11,12,13,15,17};

     printf("原始数据:");

     for ( int i = 2; i <11; i++ )

     {

         printf( "%d\t",A[i] );

     }

     merge(A,2,5,10);

 

     printf("升序合并后:");

     for ( int i = 2; i <11; i++ )

     {

         printf( "%d\t",A[i] );

     }

 

 

     int B[11] = {1,2,32,31,30,26,30,29,28,27,26 };

     printf("原始数据:");

     for ( int i = 2; i <11; i++ )

     {

         printf( "%d\t",B[i] );

     }

     merge2(B,2,5,10);

     printf("降序合并后:");

     for ( int i = 2; i <11; i++ )

     {

         printf( "%d\t",B[i] );

     }

 

     int dump = 0;

     scanf("%d",&dump);

     return 0;

}

 

//将数组A中的p-q之间和q+1到r之间的元素合并

//p-q之间和q+1-r之间的元素都是有序的

//所有元素都是升序的,要合并两个升序的数组

void merge( int* A, int p, int q, int r )

{

     int n1 = q - p + 1;

     int n2 = r - ( q + 1 ) +1;

     int* L = new int[n1+1];

     int* R = new int[n2+1];

    

 

     //复制数据

     for( int c = 0; c < n1; c++ )

     {

         L[c] = A[p+c];

 

     }

 

     for ( int c = 0; c < n2; c++)

     {

         R[c] = A[q+1+c];

     }

 

     //假设为无穷大,作为结束符

     L[n1] = 65535;

     R[n2] = 65535;

    

     int i = 0;

     int j = 0;

     //开始合并

     for ( int k = p; k <= r; k++ )

     {

         if ( L[i] <= R[j] )

         {

              A[k] = L[i];

              i++;

         }

         else

         {

              A[k] = R[j];

              j++;

         }

     }

    

 

     delete[] L;

     delete[] R;

}

 

 

 

//所有元素都是降序的,要合并两个降序的数组

void merge2( int* A, int p, int q, int r )

{

     int n1 = q - p + 1;

     int n2 = r - ( q + 1 ) +1;

     int* L = new int[n1+1];

     int* R = new int[n2+1];

 

 

     //复制数据

     for( int c = 0; c < n1; c++ )

     {

         L[c] = A[p+c];

 

     }

 

     for ( int c = 0; c < n2; c++)

     {

         R[c] = A[q+1+c];

     }

 

     //假设-1为无穷小,作为结束符

     L[n1] = -1;

     R[n2] = -1;

 

     int i = 0;

     int j = 0;

     //开始合并

     for ( int k = p; k <= r; k++ )

     {

         if ( L[i] >= R[j] )

         {

              A[k] = L[i];

              i++;

         }

         else

         {

              A[k] = R[j];

              j++;

         }

     }

 

 

     delete[] L;

     delete[] R;

}

运行结果:

就像书中所说,这其实是一个将两摞牌合并的过程,假设有两摞牌,并且其中每一摞牌都是有序的(升序或者降序),那么我们每次从这两摞牌当中取牌,然后比较牌的大小,若是升序合并的话,若取到的牌比手中的牌小,就可以将小的这张牌放到一个合并的牌堆中去了,若是降序排列的话,如果取到的牌比手中的牌大,那么就可以将大的这张牌放到合并的牌堆里面去,直至要合并的两摞牌其中有一堆已经没有牌了,那么接下来要做的是一张一张地把剩余那摞牌放到合并的牌堆里面即可,上述算法其实就是这么一个过程。

 

接下来来看看分治法解决问题的步骤:

分解:将原问题分解为一系列子问题

解决:递归地解各子问题,若子问题足够小,则直接求解

合并:将子问题的结果合并成原问题的解。

 

那么合并排序又是一个怎样的情况呢

分解:将n个元素分成含n/2个元素的子序列

解决:用合并排序法对两个子序列递归地排序

合并:合并两个已排序的子序列以得到排序结果

 

 

 

下面给出所有代码

 

//尽心,静心,精致,深入!

void merge( int* A, int p, int q, int r );

void merge2( int* A, int p, int q, int r );

void mergeSort(int* A, int p, int r);

int _tmain(int argc, _TCHAR* argv[])

{

     int C[] ={1,2,12,11,9,7,5,3,1,2,32,13,213,2,1,21,31,3,1,32,1,2,3,4,5,5,4,2,1,5,4,6,3,1,2,3,432,645,21};

     printf("合并排序递归\n");

     mergeSort(C,0,38);

     for ( int i = 0; i <39; i++ )

     {

         printf( "%d\t",C[i] );

     }

 

     int dump = 0;

     scanf("%d",&dump);

     return 0;

}

 

//将数组A中的p-q之间和q+1到r之间的元素合并

//p-q之间和q+1-r之间的元素都是有序的

//所有元素都是升序的,要合并两个升序的数组

void merge( int* A, int p, int q, int r )

{

     int n1 = q - p + 1;

     int n2 = r - ( q + 1 ) +1;

     int* L = new int[n1+1];

     int* R = new int[n2+1];

    

 

     //复制数据

     for( int c = 0; c < n1; c++ )

     {

         L[c] = A[p+c];

 

     }

 

     for ( int c = 0; c < n2; c++)

     {

         R[c] = A[q+1+c];

     }

 

     //假设为无穷大,作为结束符

     L[n1] = 65535;

     R[n2] = 65535;

    

     int i = 0;

     int j = 0;

     //开始合并

     for ( int k = p; k <= r; k++ )

     {

         if ( L[i] <= R[j] )

         {

              A[k] = L[i];

              i++;

         }

         else

         {

              A[k] = R[j];

              j++;

         }

     }

    

 

     delete[] L;

     delete[] R;

}

 

 

 

//所有元素都是降序的,要合并两个降序的数组

void merge2( int* A, int p, int q, int r )

{

     int n1 = q - p + 1;

     int n2 = r - ( q + 1 ) +1;

     int* L = new int[n1+1];

     int* R = new int[n2+1];

 

 

     //复制数据

     for( int c = 0; c < n1; c++ )

     {

         L[c] = A[p+c];

 

     }

 

     for ( int c = 0; c < n2; c++)

     {

         R[c] = A[q+1+c];

     }

 

     //假设-1为无穷小,作为结束符

     L[n1] = -1;

     R[n2] = -1;

 

     int i = 0;

     int j = 0;

     //开始合并

     for ( int k = p; k <= r; k++ )

     {

         if ( L[i] >= R[j] )

         {

              A[k] = L[i];

              i++;

         }

         else

         {

              A[k] = R[j];

              j++;

         }

     }

 

 

     delete[] L;

     delete[] R;

}

 

 

//合并排序之升序

void mergeSort(int* A, int p, int r)

{

     //若p=r其实已经是一个数了

     if( p < r )

     {

         //分割任务,直至为单独一个数字

         int q = ( p + r )/2;

         mergeSort( A, p, q );

         mergeSort( A, q+1, r );

         merge(A,p,q,r);

     }

}

 

//合并排序之降序

void mergeSort2(int* A, int p, int r)

{

     //若p=r其实已经是一个数了

     if( p < r )

     {

         //分割任务,直至为单独一个数字

         int q = ( p + r )/2;

         mergeSort2( A, p, q );

         mergeSort2( A, q+1, r );

         merge2(A,p,q,r);

     }

}

 

 

运行结果:


合并排序最为精辟的莫过于

void mergeSort(int* A, int p, int r)

{

     //若p=r其实已经是一个数了

     if( p < r )

     {

         //分割任务,直至为单独一个数字

         int q = ( p + r )/2;

         mergeSort( A, p, q );

         mergeSort( A, q+1, r );

         merge(A,p,q,r);

     }

}

分析:

其基本思想就是将一个任务分割为1/2,直至p=r的时候,也就是把很多数首先分为两组,然后继续分割,分为4组,直至最后每个数都各自成为一组,接下来要做的就是使用merge来合并两个数字,使之有序,然后继续合并合并合并,直至最后整体有序,实在精妙。

 

 

合并排序算法复杂度分析:

合并排序的算法复杂度为O(nlgn),lg是以2为底,不是以10为底。

至于具体的如何计算出复杂度的,这里我就不详细说明了。

 

练习:

2.3-1以图2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。

(1)首先将每个数各自形成一组即

<3>,<41>,<52>,<26>,<38>,<57>,<9>,<49>

 

(2)接下来使用合并方法进行排序

<3,41>,<26,52>,<38,57>,<9,49>

 

(3)继续合并排序

<3,26,41,52>,<9,38,49,57>

 

(4)继续合并排序

<3,9,26,38,41,49,52,57>

OK排序成功

 

 

2.3-2改写MERGE过程,使之不用哨兵元素,而是在一旦元素L或R中所有元素都被复制回数组后,就立即停止,再将另一个数组中余下的元素复制回数组A中。

下面给出算法代码

//改写的代码

//升序合并排列

void merge_1( int* A, int p, int q, int r)

{

     int n1 = q - p + 1;

     int n2 = r - q;

     int* L = new int[n1];

     int* R = new int[n2];

 

     for( int c = 0; c < n1; c++ )

     {

         L[c] = A[p+c];

 

     }

 

     for ( int c = 0; c < n2; c++ )

     {

         R[c] = A[q+1+c];

     }

 

 

    

     int i = 0;

     int j = 0;

     int k = 0;

     while ( i < n1 && j< n2 )

     {

         if ( L[i] <= R[j] )

         {

              A[k] = L[i];

              i++;

         }

         else

         {

              A[k] = R[j];

              j++;

         }

 

         k++;

     }

 

     //即L空了,那么只剩下R了

     if ( i == n1 )

     {

         while ( j < n2 )

         {

              A[k] = R[j];

              j++;

         }

     }

     else//即R空了,那么只剩下L了

     {

         while ( i < n1 )

         {

              A[k] = L[i];

              i++;

         }

     }

}

评论:

其实在没有哨兵的情况下可以将L和R中的元素个数n1和n2作为结束条件,然后再判断究竟是哪个数组(L或者R)完成了,然后接着完成剩余的数组的复制工作即可大功告成。

 

 

 

2.3-2利用数学归纳法证明:当n是2的整数次幂时,递归式

当n=2 时,T(n)=2

当n=2^k,对于k>1时, T(n)=2T(n/2)+n

 

证明:

①当n=2时,T(n)=2

②当n1=2^k时候,此时T(2^k)=2T(2^k/2)+2^k

③当n2=2^(k+1)时,此时由②得T(2^(k+1))=2T(2^(k+1)/2)+2^(k+1)

因为T(2^(k+1))=2T(2^k)+2^(k+1)④

又因为T(2^k)=2T(2^k/2)+2^k

将其带入④得

T(2^(k+1))=2*(2T(2^k/2)+2^k)+2^(k+1)=2^2T(2^(k-1))+2^(k+2)

依次类推可以得到T(n)=nlgn

不知道我写的对不对-_-!!!!!

 

 

2.3-4插入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]插入到已排序的数组A[1,n-1]中去,对于插入排序的这一递归版本,为它的运行时间写一个递归式。

如果n=1,T(n)=O(1)

如果n>1,T(n)=T(n-1)+O(n)

所以T(n)=O(n^2)

 

2.3-5回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的重点与v进行比较。根据比较的结果,原序列中有一半就可以不用再做进一步的考虑了。二分查找就是一个不断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做进一步查找,写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找算法的最坏情况运行时间为什么是O(lgn)。


今天就看到这里吧.....

你可能感兴趣的:(算法导论读书笔记2)