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[]) {undefined int A[11] ={1,2,1,12,13,16,11,12,13,15,17}; printf("原始数据:"); for ( int i = 2; i <11; i++ ) {undefined printf( "%d\t",A[i] ); } merge(A,2,5,10); printf("升序合并后:"); for ( int i = 2; i <11; i++ ) {undefined 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++ ) {undefined printf( "%d\t",B[i] ); } merge2(B,2,5,10); printf("降序合并后:"); for ( int i = 2; i <11; i++ ) {undefined 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 ) {undefined 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++ ) {undefined L[c] = A[p+c]; } for ( int c = 0; c < n2; c++) {undefined 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++ ) {undefined if ( L[i] <= R[j] ) {undefined A[k] = L[i]; i++; } else {undefined A[k] = R[j]; j++; } } delete[] L; delete[] R; } //所有元素都是降序的,要合并两个降序的数组 void merge2( int* A, int p, int q, int r ) {undefined 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++ ) {undefined L[c] = A[p+c]; } for ( int c = 0; c < n2; c++) {undefined 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++ ) {undefined if ( L[i] >= R[j] ) {undefined A[k] = L[i]; i++; } else {undefined 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[]) {undefined 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++ ) {undefined 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 ) {undefined 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++ ) {undefined L[c] = A[p+c]; } for ( int c = 0; c < n2; c++) {undefined 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++ ) {undefined if ( L[i] <= R[j] ) {undefined A[k] = L[i]; i++; } else {undefined A[k] = R[j]; j++; } } delete[] L; delete[] R; } //所有元素都是降序的,要合并两个降序的数组 void merge2( int* A, int p, int q, int r ) {undefined 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++ ) {undefined L[c] = A[p+c]; } for ( int c = 0; c < n2; c++) {undefined 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++ ) {undefined if ( L[i] >= R[j] ) {undefined A[k] = L[i]; i++; } else {undefined A[k] = R[j]; j++; } } delete[] L; delete[] R; }
//合并排序之升序
void mergeSort(int* A, int p, int r) {undefined //若p=r其实已经是一个数了 if( p < r ) {undefined //分割任务,直至为单独一个数字 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) {undefined //若p=r其实已经是一个数了 if( p < r ) {undefined //分割任务,直至为单独一个数字 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) {undefined //若p=r其实已经是一个数了 if( p < r ) {undefined //分割任务,直至为单独一个数字 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来合并两个数字,使之有序,然后继续合并合并合并,直至最后整体有序,实在精妙。
原文链接:https://blog.csdn.net/xizero00/article/details/7406318