算法之歸並排序的遞歸與非遞歸的實現


一.什么是歸並排序

    歸並排序就是將多個有序的數據段合成一個有序的數據段,如果參與合並的只有兩個有序的數據段,則稱為二路歸並。與快速排序和堆排序相比,其最大的特點是一種穩定的算法,算法的平均時間復雜度O(nlog2n)。

二.歸並排序的基本思路

    (1).對於一個原始的待排序表,可以將R[1]到R[n]可以看做是n個長度為1的有序表,即分解。

    (2).進行第一趟歸並,即將上述的n個子序兩兩合並,得到 n/2向上取整 個有序表,若n為奇數,則歸並到最后一個子序列長度為1,即合並。

    (3).再將兩個 n/2向上取整 個有序表兩兩合並,如此重復下去,直到得到一個長度為n的有序表,這種排序方法也叫2-路歸並排序。

     原理如下圖

 

                                         

                                                      以[63,95,84,46,18,24,27,31,46]為例

三. 實現歸並排序之前,需要調用"一次歸並“和”一趟歸並“,那什么是一次歸並?什么是一趟歸並呢?

     (1).一次歸並:把首尾相接的兩個有序表R[low...mid]、R[mid+1...high]歸並成有序表R[low...high].

      (2) .一趟歸並:把一些相互連接的有序表依次兩兩歸並成一些更大的有序表。

 

 1.一次歸並代碼如下

//一次歸並排序 
void Merge(int low,int m,int high)         
{                // 將兩個有序的子文件R[low..m)和R[m+1..high]歸並成一個有序的子文件R[low..high]        
    int i=low;
    int j=m+1;
    int p=0;            //置初始值      
    int *R1;            //R1是局部向量    
    R1=(int *)malloc((high-low+1)*sizeof(int));      
    if(!R1)                        //申請空間失敗       
    {         
       puts("空間申請失敗");        
       return;       
    }       
    while(i<=m&&j<=high)                  // 兩子文件非空時取其小者輸出到R1[p]上       
        R1[p++]=(R[i]<=R[j])?R[i++]:R[j++];       
    while(i<=m)                          // 若第1個子文件非空,則復制剩余記錄到R1中         
        R1[p++]=R[i++];       
    while(j<=high)                      // 若第2個子文件非空,則復制剩余記錄到R1中      
        R1[p++]=R[j++];       
    for(p=0,i=low;i<=high;p++,i++)         
        R[i]=R1[p];                    // 歸並完成后將結果復制回R[low..high] 
}   

2.一趟歸並排序代碼如下

 1 //一趟歸並排序 
 2 void mergepass(int n,int len) 
 3 {
 4     int i,t;
 5     i=1;
 6     while(i<=n-2*len+1)
 7     {
 8         Merge(i,i+len-1,i+2*len-1);
 9         i=i+2*len;
10     }
11     if(i+len-1<n)
12        Merge(i,i+len-1,n);
13 }

 

   寫完”一次歸並“和"一趟歸並”后,接下來就是編寫二路歸並了,二路歸並就是調用“一次歸並”和“一趟歸並”,最后組合成一個長度為n的有序表。

二路歸並有遞歸的方法和非遞歸的方法(即迭代方法),下面就來看一下遞歸與非遞歸的實現

(1)非遞歸的方法

 1 void mergesort(int n)       //非遞歸的二路歸並實現
 2 {
 3     int len;
 4     int *R1;            // R1是局部向量
 5     len=1;
 6     while(len<n)
 7     {
 8       mergepass(n,len);
 9       len=2*len;
10     }
11 }

(2)遞歸方法實現

 1 //遞歸的二路歸並排序 
 2 void MergeSortDC(int low,int high) 
 3 {                                     //用分治法對R[low..high]進行二路歸並排序        
 4    int mid;        
 5    if(low<high)         
 6    {                                 // 區間長度大於1          
 7      mid=(low+high)/2;                //分解 */    
 8      MergeSortDC(low,mid);           // 遞歸地對R[low..mid]排序    
 9      MergeSortDC(mid+1,high);           //遞歸地對R[mid+1..high]排序           
10      Merge(low,mid,high);               // 組合,將兩個有序區歸並為一個有序區          
11    }  
12 }   

     講到這里,歸並排序的思想方法就講完了,然后就可以自己寫main()函數了,然后調用上面的自定義函數就可以實現了

下面是完整的代碼實現,僅供參考,如有問題,歡迎指教

  1 #include <stdio.h>  
  2 #include<malloc.h>
  3 #include<stdlib.h>
  4 #define MAX 100 
  5 int R[MAX];   
  6 //一次歸並排序 
  7 void Merge(int low,int m,int high)         
  8 {                // 將兩個有序的子文件R[low..m)和R[m+1..high]歸並成一個有序的子文件R[low..high]        
  9     int i=low;
 10     int j=m+1;
 11     int p=0;            //置初始值      
 12     int *R1;            //R1是局部向量    
 13     R1=(int *)malloc((high-low+1)*sizeof(int));      
 14     if(!R1)                        //申請空間失敗       
 15     {         
 16        puts("空間申請失敗");        
 17        return;       
 18     }       
 19     while(i<=m&&j<=high)                  // 兩子文件非空時取其小者輸出到R1[p]上       
 20         R1[p++]=(R[i]<=R[j])?R[i++]:R[j++];       
 21     while(i<=m)                          // 若第1個子文件非空,則復制剩余記錄到R1中         
 22         R1[p++]=R[i++];       
 23     while(j<=high)                      // 若第2個子文件非空,則復制剩余記錄到R1中      
 24         R1[p++]=R[j++];       
 25     for(p=0,i=low;i<=high;p++,i++)         
 26         R[i]=R1[p];                    // 歸並完成后將結果復制回R[low..high] 
 27 }   
 28 //遞歸的二路歸並排序 
 29 void MergeSortDC(int low,int high) 
 30 {                                     //用分治法對R[low..high]進行二路歸並排序        
 31    int mid;        
 32    if(low<high)         
 33    {                                 // 區間長度大於1          
 34      mid=(low+high)/2;                //分解 */    
 35      MergeSortDC(low,mid);           // 遞歸地對R[low..mid]排序    
 36      MergeSortDC(mid+1,high);           //遞歸地對R[mid+1..high]排序           
 37      Merge(low,mid,high);               // 組合,將兩個有序區歸並為一個有序區          
 38    }  
 39 }   
 40 //一趟歸並排序 
 41 void mergepass(int n,int len) 
 42 {
 43     int i,t;
 44     i=1;
 45     while(i<=n-2*len+1)
 46     {
 47         Merge(i,i+len-1,i+2*len-1);
 48         i=i+2*len;
 49     }
 50     if(i+len-1<n)
 51        Merge(i,i+len-1,n);
 52 }
 53 void mergesort(int n) 
 54 {
 55     int len;
 56     int *R1;            // R1是局部向量
 57     len=1;
 58     while(len<n)
 59     {
 60       mergepass(n,len);
 61       len=2*len;
 62     }
 63 }
 64 void main() 
 65 {  
 66     int i,n,d;     
 67     puts("請輸入數組的長度:");
 68     scanf("%d",&n);   
 69     if(n<=0||n>MAX)  
 70     {   
 71         printf("n 必須大於 0 小於 %d.\n",MAX);   
 72         exit(0);  
 73     }   
 74     puts("請依次輸入數組的數據:");  
 75     for(i=1;i<=n;i++)   
 76       scanf("%d",&R[i]);   
 77     puts("排序前的數組數據為:");  
 78     for(i=1;i<=n;i++)   
 79        printf("%4d",R[i]);  
 80    printf("\n\n      遞歸與非遞歸的合並排序       \n");
 81    printf("\n      1.遞歸合並排序               \n");
 82    printf("\n      2.非遞歸合並排序             \n");
 83    printf("\n請輸入您的選擇:");
 84    scanf("%d",&d);
 85    if(d==1)
 86    {
 87     MergeSortDC(1,n);   
 88     puts("\n遞歸歸並排序后:");  
 89     for(i=1;i<=n;i++)   
 90        printf("%4d",R[i]);  
 91    } 
 92    else if(d==2) 
 93    {
 94        mergesort(n);
 95        puts("\n非遞歸歸並排序后:");  
 96     for(i=1;i<=n;i++)   
 97        printf("%4d",R[i]); 
 98    }
 99    else
100      printf("您輸入的菜單號有誤!"); 
101 }

 

      

 

 

 

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM