归并排序之非递归版及优化探讨

本文探讨的是2-路归并排序的算法,在空间复杂度上,需要与待排记录等数量的辅助空间;时间复杂度上,为O(nlogn)。相比较于快速排序与堆排序而言,归并排序的最大特点就是,它是一种稳定的排序算法。书上给出了其递归算法的实现,本人就自己写的非递归版本,在效率与优化上做一点比较。

递归版本

  首先先给出递归版本的实现,编译环境:VS2012 

统一定义的头文件:

 

 /* c10-1.h 待排记录的数据类型 */
 #define MAXSIZE 20 /* 一个用作示例的小顺序表的最大长度 */
 typedef int KeyType; /* 定义关键字类型为整型 */
 typedef struct
 {
   KeyType key; /* 关键字项 */
   InfoType otherinfo; /* 其它数据项,具体类型在主程中定义 */
 }RedType; /* 记录类型 */

 typedef struct
 {
   RedType r[MAXSIZE+1]; /* r[0]闲置或用作哨兵单元 */
   int length; /* 顺序表长度 */
 }SqList; /* 顺序表类型 */

 

 

然后再给出具体的实现,附上测试:将10个关键字的记录重复测试排序一千万次,计算所需时间

 /* alg10-10.c 归并排序 */
#pragma warning(disable: 4996)

 #include<stdio.h>
#include <conio.h>
 typedef int InfoType; /* 定义其它数据项的类型 */
 #include "C10-1.h"
 #include "time.h"

 /* c9.h 对两个数值型关键字的比较约定为如下的宏定义 */
 #define EQ(a,b) ((a)==(b))
 #define LT(a,b) ((a)<(b))
 #define LQ(a,b) ((a)<=(b))


 void Merge(RedType SR[],RedType TR[],int i,int m,int n)
 { /* 将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n] 算法10.12 */
   int j,k,l; 
   for(j=m+1,k=i;i<=m&&j<=n;++k) /* 将SR中记录由小到大地并入TR */
     if LQ(SR[i].key,SR[j].key)
       TR[k]=SR[i++];
     else
       TR[k]=SR[j++];
   if(i<=m)
     for(l=0;l<=m-i;l++)
       TR[k+l]=SR[i+l]; /* 将剩余的SR[i..m]复制到TR */
   if(j<=n)
     for(l=0;l<=n-j;l++)
       TR[k+l]=SR[j+l]; /* 将剩余的SR[j..n]复制到TR */
 }

 void MSort(RedType SR[],RedType TR1[],int s, int t)
 { /* 将SR[s..t]归并排序为TR1[s..t]。算法10.13 */
   int m;
   RedType TR2[MAXSIZE+1];
   if(s==t)
     TR1[s]=SR[s];
   else
   {
     m=(s+t)/2; /* 将SR[s..t]平分为SR[s..m]和SR[m+1..t] */
     MSort(SR,TR2,s,m); /* 递归地将SR[s..m]归并为有序的TR2[s..m] */
     MSort(SR,TR2,m+1,t); /* 递归地将SR[m+1..t]归并为有序的TR2[m+1..t] */
     Merge(TR2,TR1,s,m,t); /* 将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t] */
   }
 }  
 void MergeSort(SqList *L)
 { /* 对顺序表L作归并排序。算法10.14 */
   MSort((*L).r,(*L).r,1,(*L).length);
 }

 void print(SqList L)
 {
   int i;
   for(i=1;i<=L.length;i++)
     printf("(%d,%d)",L.r[i].key,L.r[i].otherinfo);
   printf("\n");
 }

 #define N 10
 void main()
 {
   RedType d[N]={{49,1},{38,2},{97,3},{32,4},{76,5},{13,6},{12,7},{2,8},{3,9},{45,10}};
   SqList l;
   int i,t;
   clock_t a,b;
   double c;
  
   loop:
   for(i=0;i<N;i++)
     l.r[i+1]=d[i];
   l.length=N;
   printf("排序前:\n");
   print(l);
   a=clock();

   /*测试用时*/
   for(i=0;i<10000000;i++)
   {
       for(t=0;t<N;t++)
           l.r[t+1]=d[t];
       MergeSort(&l);
   }
   b=clock();
   c=(double)(b-a)/1000;
   printf("用时为%f秒,%d,%d\n",c,a,b);
   printf("排序后:\n");
   print(l);
   while(1)
   {
       printf("是否继续(y/n):"); 
           if((t=getche(),printf("\n\n"),t)=='y')
           goto loop;
           if(t=='n')
           break;
   }
 }

 

在debug版本上,所需时间大约为15.0s,但在release版优化后,就只需2.74s

             

 

 

非递归版本

下面就晒出自己的非递归版本,由于代码写的实在垃圾,无论是debug,还是release都毫无优势,就不做具体分析了,读者自行查看  

 /* alg10-10.c 归并排序(非递归版) */
#pragma warning(disable: 4996)

#include <stdio.h>
#include <conio.h>
#include <malloc.h>
 typedef int InfoType; /* 定义其它数据项的类型 */
 #include "C10-1.h"
 #include "time.h" 
  #define N 10

 /* c9.h 对两个数值型关键字的比较约定为如下的宏定义 */
 #define EQ(a,b) ((a)==(b))
 #define LT(a,b) ((a)<(b))
 #define LQ(a,b) ((a)<=(b))


 void Merge(RedType SR[],RedType TR[],int i,int m,int n)
 { /* 将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n] 算法10.12 */
   int j,k,l; 
   for(j=m+1,k=i;i<=m&&j<=n;++k) /* 将SR中记录由小到大地并入TR */
     if LQ(SR[i].key,SR[j].key)
       TR[k]=SR[i++];
     else
       TR[k]=SR[j++];
   if(i<=m)
     for(l=0;l<=m-i;l++)
       TR[k+l]=SR[i+l]; /* 将剩余的SR[i..m]复制到TR */
   if(j<=n)
     for(l=0;l<=n-j;l++)
       TR[k+l]=SR[j+l]; /* 将剩余的SR[j..n]复制到TR */
 }


 int twopow(int i)  //此处算2的幂
 {
    int j=1;
    for(;i>0;i--)
        j*=2;
    return j;
 }

 void MergeSort(SqList *L)
 { /* 对顺序表L作归并排序。算法10.14 */
  // MSort((*L).r,(*L).r,1,(*L).length);
     int i,j,k;
     SqList *L1=(SqList *)malloc(sizeof(SqList));
     L1->length=N;
     for(i=1;i<=N;i++)
             L1->r[i]=L->r[i];
     for(j=1;k=twopow(j-1),(L->length)/k>=1;j++)  //确定归并的次数
     {
         for(i=1;(i+k)<=L->length;i+=k*2)
         {  
             if((i+k)==L->length||i+2*k-1>=L->length) 
             {
                 Merge(L->r,L1->r,i,i+k-1,L->length);
                 continue;
             }
                 Merge(L->r,L1->r,i,i+k-1,i+2*k-1);
         }
         for(i=1;i<=N;i++)
             L->r[i]=L1->r[i];
     }
     free(L1);
 }

 void print(SqList L)
 {
   int i;
   for(i=1;i<=L.length;i++)
     printf("(%d,%d)",L.r[i].key,L.r[i].otherinfo);
   printf("\n");
 }


 void main()
 {
     RedType d[N]={{49,1},{38,2},{97,3},{32,4},{76,5},{13,6},{12,7},{2,8},{3,9},{45,10}};
   SqList l;
   int i,t;
   clock_t a,b;
   double c;
  
   loop:
   for(i=0;i<N;i++)
     l.r[i+1]=d[i];
   l.length=N;
   printf("排序前:\n");
   print(l);
   a=clock();

   /*测试用时*/
   //for(i=0;i<10000000;i++)
     for(i=0;i<10000000;i++)
   {
       for(t=0;t<N;t++)
           l.r[t+1]=d[t];
       MergeSort(&l);
   }
   
   b=clock();
   c=(double)(b-a)/1000;
   printf("用时为%f秒,%d,%d\n",c,a,b);
   printf("排序后:\n");
   print(l);
   while(1)
   {
       printf("是否继续(y/n):"); 
           if((t=getche(),printf("\n\n"),t)=='y')
           goto loop;
           if(t=='n')
           break;
   }
 }
View Code

          

  

 

下面就上面的代码,做一下彻底优化,把一些没必要的冗余计算,比如要算出具体的归并趟数,重复的赋值操作给去掉,得到相对高效的代码

 /* alg10-10.c 归并排序(非递归版2) */
#pragma warning(disable: 4996)

#include <stdio.h>
#include <conio.h>
#include <malloc.h>
 typedef int InfoType; /* 定义其它数据项的类型 */

 #include "C10-1.h"
 #include "time.h" 
  #define N 10

 /* c9.h 对两个数值型关键字的比较约定为如下的宏定义 */
 #define EQ(a,b) ((a)==(b))
 #define LT(a,b) ((a)<(b))
 #define LQ(a,b) ((a)<=(b))


 void Merge(RedType SR[],RedType TR[],int i,int m,int n)
 { /* 将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n] 算法10.12 */
   int j,k,l; 
   /*int i0=i;*/
   for(j=m+1,k=i;i<=m&&j<=n;++k) /* 将SR中记录由小到大地并入TR */
     if LQ(SR[i].key,SR[j].key)
       TR[k]=SR[i++];
     else
       TR[k]=SR[j++];
   if(i<=m)
     for(l=0;l<=m-i;l++)
       TR[k+l]=SR[i+l]; /* 将剩余的SR[i..m]复制到TR */
   //if(j<=n)
   //  for(l=0;l<=n-j;l++)
   //    TR[k+l]=SR[j+l]; /* 将剩余的SR[j..n]复制到TR */
   /*for(l=i0;l<=j;l++)
       SR[l]=TR[l];*/
 }

  void MergeSort(SqList *L)
  {
        int step = 1,i,right;
        int j;
        SqList *L1=(SqList *)malloc(sizeof(SqList));
        L1->length=N;
        for(i=1;i<=N;i++)
             L1->r[i]=L->r[i];
        while(step < N)
        {
            for(i = 1; i +step<= N; i += 2*step)
            {
                if(i+step-1 == N||i+2*step-1>=N) 
                    right = N;
                else 
                    right = i+step*2-1;
                Merge(L->r,L1->r , i, i+step-1, right);
                for(j=i;j<=right;j++)
                    L->r[j]=L1->r[j];
            }
            step *= 2;
        }
            free(L1);
  }


 void print(SqList L)
 {
   int i;
   for(i=1;i<=L.length;i++)
     printf("(%d,%d)",L.r[i].key,L.r[i].otherinfo);
   printf("\n");
 }


 void main()
 {
     RedType d[N]={{49,1},{38,2},{97,3},{32,4},{76,5},{13,6},{12,7},{2,8},{3,9},{45,10}};
   SqList l;
   int i,t;
   clock_t a,b;
   double c;
  
   loop:
   for(i=0;i<N;i++)
     l.r[i+1]=d[i];
   l.length=N;
   printf("排序前:\n");
   print(l);
   a=clock();

   /*测试用时*/
     for(i=0;i<10000000;i++)
   {
       for(t=0;t<N;t++)
           l.r[t+1]=d[t];
       MergeSort(&l);
   }
   
   b=clock();
   c=(double)(b-a)/1000;
   printf("用时为%f秒,%d,%d\n",c,a,b);
   printf("排序后:\n");
   print(l);
   while(1)
   {
       printf("是否继续(y/n):"); 
           if((t=getche(),printf("\n\n"),t)=='y')
           goto loop;
           if(t=='n')
           break;
   }
 }

 测试结果如下:

                  

         

 

显然,Debug版本的效率要比递归版本的要快,但Release版本优化后的效率还是没有递归版的优化效果好。显然在某些时候,递归形式的写法,虽然理解上费劲,(其实数学归纳法学的好,写正确的递归代码并非难事),实用性较差,很多人为了代码的可读性,都不建议写递归形式,虽然形式上简洁。甚至在一般情况下,递归的写法,会因为频繁的入栈出栈而损耗效率。可是事实也许并非如此,编译器对递归函数的优化,有时候明显要比一般的非递归算法要强。

                         

posted @ 2013-06-16 15:22  登高行远  阅读(525)  评论(0编辑  收藏  举报