算法进化历程之“归并排序”

来源:互联网 发布:华泰证券期货软件 编辑:程序博客网 时间:2024/06/08 11:53

算法进化历程之“归并排序”

巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo

 

归并排序是分治算法的典型运用,其中先分治,再合并的思路真是美妙至极。

归并排序算法的基本操作是合并两个已排序的表,这只需要线性的时间,不足之处是需要分配一个临时数组来暂存数据。

归并排序算法可以用递归的形式实现,形式简洁易懂。如果N=1,则只有一个元素需要排序,我们可以什么都不做;否则,递归地将前半部分数据和后半部分数据各自归并排序,然后合并这两个部分。

归并排序算法也可以用非递归的形式实现,稍微难理解一点。它刚好是递归分治算法的逆向思维形式,在使用递归分治算法时,程序员只需考虑将一个大问题分成若干个形式相同的小问题,和解的边界条件,具体如何解决这些小问题是由计算机自动完成的;而非递归形式要求程序员从最基本的情况出发,即从解决小问题出发,一步步扩展到大问题。

一点说明:很多人在写递归形式的归并排序算法时,临时数组是在归并排序子程序 MSort ()中分配的,这使得 在任一时刻都可能有 logN 个临时数组处在活动期,如果数据较多,则开销很大,实用性很差 。我在版本1中把临时数组设置在合并函数 Merge ()中,避免了这个问题;在版本2中进一步优化,把临时数组设置在归并排序驱动程序MergeSort()中,只需执行一次分配空间的函数就行了。

版本一:普通的合并算法

       网络上贴出的归并排序算法版本很多,多数采用的普通的合并函数 Merge (),即先将存储在A[]中的两个已排序序列按非递减序归并入tempA[],然后将A[]中剩余的元素复制到tempA[],最后将tempA[]中元素复制回A[]。算法简明易懂,代码如下:

 

void MergeSort_1(ElemType A[], int n)//归并排序驱动程序

{

       MSort_1(A,0, n-1);

}

 

void MSort_1(ElemType A[], int left, intright)//归并排序子程序

{

       intmid = (left + right) / 2;

      

       if(left < right)

       {

              MSort_1(A,left, mid);

              MSort_1(A,mid+1, right);

              Merge_1(A,left, mid, right);

       }

}

 

void Merge_1(ElemType A[], int left, intmid, int right)//普通合并程序

{

       inti, j, k;

       ElemType*tempA = malloc((right-left+1)*sizeof(ElemType));

      

       if(!tempA)

       {

              printf("Outof space!");

              exit(1);

       }

      

       k= 0;

       for(i=left, j=mid+1; i<=mid && j<=right; )//将A[]中元素按非递减序归并入tempA[]

       {

              if(A[i] <= A[j])

                     tempA[k++]= A[i++];

              else

                     tempA[k++]= A[j++];

       }

       //将A[]中剩余的元素复制到tempA[] 

       while(i <= mid)

              tempA[k++]= A[i++];

      

       while(j <= right)

              tempA[k++]= A[j++];

      

     //将tempA[]中元素复制回A[]

   for (i=left,j=0; j<k; i++,j++)

           A[i] = tempA[j];

 

       free(tempA);

}

 

版本二:高效的合并算法

Mark Allen Weiss在其著作《数据结构与算法分析——C语言描述》中介绍了一个高效的归并排序算法,他把临时数组设置在归并排序驱动程序MergeSort()中,只需执行一次分配空间的函数就行了。

此外,还可以对普通的合并函数进行优化,只需把两个已排序序列“相向复制”到临时数组tempA[],然后归并入A[]即完成排序。所谓“相向复制”是指第一个序列顺序存储到tempA[],第二个序列逆序存储到tempA[];执行合并操作的时候从左向右扫描序列1,从右向左扫描序列2,正好“相向而行”。这样处理的好处是无需判断两个分序列是否越界,只要合并完所有元素,循环自动结束。

代码如下:

void MergeSort_2(ElemType A[], int n)//归并排序驱动程序

{

       ElemType*tempA = malloc(n*sizeof(ElemType));

      

       if(!tempA)

       {

              printf("Outof space!");

              exit(1);

       }

      

       MSort_2(A,tempA, 0, n-1);

free(tempA);

}

 

void MSort_2(ElemType A[], ElemTypetempA[], int left, int right)//归并排序子程序

{

       intmid = (left + right) / 2;

      

       if(left < right)

       {

              MSort_2(A,tempA, left, mid);

              MSort_2(A,tempA, mid+1, right);

              Merge_2(A,tempA, left, mid, right);

       }

}

 

void Merge_2(ElemType A[], ElemTypetempA[], int left, int mid, int right)//高效合并程序

{

       inti, j, k;

 

       for(i=left; i<=mid; i++)//将A[left..mid]中元素顺序复制到tempA[left..mid]

              tempA[i]= A[i];

      

       for(i=mid+1,j=right; i<=right; i++,j--)//将A[mid+1..right]中元素逆序序复制到tempA[mid+1..right]

           tempA[j] = A[i];

      

       for(k=i=left, j=right; k<=right; )//巧妙安排扫描方向,只需判断k是否越界

       {

              if(tempA[i] <= tempA[j])

                     A[k++]= tempA[i++];

              else

                     A[k++]= tempA[j--];   

       }

}

 

版本三:非递归算法

归并排序的递归算法简明易懂,但毕竟要多次递归调用,需要额外的递归栈空间,效率不如非递归算法高。

       网络上各种非递归算法也是五花八门,良莠不齐,我就不一一介绍了,只贴出我最喜欢的一款——也是原创的一款,呵呵。

       简单介绍一下算法思路:分别用beforeLen和afterLen表示合并前后序列的长度,其中合并后序列的长度是合并前的两倍。

       初始条件beforeLen=1,之后每执行一次合并操作,beforeLen增倍(beforeLen=afterLen)。

外部 for 循环的循环体每执行一次,都使 beforeLen 和 afterLen 加倍。内部的for循环执行序列的合并工作,它的循环体每执行一次,i 都向前移动 afterLen 个位置,合并相邻的两个等量序列,直到剩余元素的数量小于afterLen。若剩下的元素数量介于beforeLen和afterLen之间,说明这是两个数量不等的序列,对他们进行合并操作;若剩下的元素数量不多于beforeLen,说明这些元素属于同一个已排序的序列,无需合并。

       合并函数采用了高效的Merge_2()。代码如下:

 

void MergeSort_3(ElemType A[], int n)//归并排序主程序(非递归)

{

       inti, c=0;

       intbeforeLen; // 合并前序列的长度

       intafterLen = 1;// 合并后序列的长度

       ElemType*tempA = malloc(n*sizeof(ElemType));

      

       if(!tempA)

       {

              printf("Outof space!");

              exit(1);

       }

 

    for (beforeLen=1; afterLen<n;beforeLen=afterLen)

    {

           afterLen = beforeLen << 1; //合并后序列的长度是合并前的两倍

          

              for(i=0; i+afterLen<=n; i+=afterLen) //两两合并相邻的两个等量序列

                     Merge_2(A,tempA, i, i+beforeLen-1, i+afterLen-1);

                    

       if (i+beforeLen < n)//合并两个数量不等的序列

                     Merge_2(A,tempA, i, i+beforeLen-1, n-1);

    }


free(tempA);

}

 

       本篇文章中的Merge_2()和MergeSort_3()均属原创,虽然经过小规模数据的测试,但也可能存在纰漏,欢迎批评指正,谢谢!

0 0
原创粉丝点击