一.什么是歸並排序
歸並排序就是將多個有序的數據段合成一個有序的數據段,如果參與合並的只有兩個有序的數據段,則稱為二路歸並。與快速排序和堆排序相比,其最大的特點是一種穩定的算法,算法的平均時間復雜度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 }