我們知道,遞迴實現的缺點就是會一直呼叫棧,而棧記憶體往往是很小的。所以,我們嘗試著用迴圈的辦法去實現歸併排序。
之氣提到過,歸併排序的基本思路是將待排序序列r[0…n-1]看成是n個長度為1的有序序列,將相鄰的有序表成對歸併,得到n/2個長度為2的有序表;將這些有序序列再次歸併,得到n/4個長度為4的有序序列;如此反覆進行下去,最後得到乙個長度為n的有序序列。
綜上可知:
歸併排序其實要做兩件事:
(1)「分解」——將序列每次折半劃分。
(2)「合併」——將劃分後的序列段兩兩合併後排序。
具體過程如下圖所示:
1.待排序序列r[0…n-1] 一共有n個有序的子串行,每個子串行裡都只含有乙個元素。
2. 某趟歸併中,設各子表的長度為length,則歸併前r[0…n-1]中共有n/length個有序的子表:r[0…length-1], r[gap…2*length-1], … , r[(n/length)*length … n-1]。
3. 在上面歸併的時候,我們遇到的length(1,2,4,8…)很可能不會在最終結果等於n。比如:n=7。這時候,我們採取的一種辦法是只歸併前面最接近n的那個位置(i+2*length<=n,即i<=n-2*length,處理到倒數第二對)。剩下的就通過下面**來實現:
if ( i+length
< n ) /*說明i+乙個子串行的長度*/
merge( a, tmpa, i, i+length, n-1);/* 歸併最後2個子列*/
else
/* 最後只剩1個子列*/
for ( j = i; j < n; j++ ) tmpa[j] = a[j];
n=7的示意圖:
完整**:
#include "stdio.h"
#include "stdlib.h"
void merge( int a, int tmpa, int l, int r, int rightend )
while( l <= leftend )
tmpa[tmp++] = a[l++]; /* 直接複製左邊剩下的 */
while( r <= rightend )
tmpa[tmp++] = a[r++]; /* 直接複製右邊剩下的 */
/*for( i = 0; i < numelements; i++, rightend -- )
a[rightend] = tmpa[rightend]; 將有序的tmpa複製回a */
}/* length = 當前有序子列的長度*/
void merge_pass( int a, int tmpa, int n, int length )
void merge_sort( int a, int n )
free( tmpa );
}else
printf( "空間不足" );
}int main()
; //int a[10]= ;
merge_sort(a,10);
for(int i = 0;i<10;i++)
printf("%d ",a[i]);
return
0;}
當然,我們在上面**的第24行中,將原先遞迴實現時每次歸併後儲存在tempa陣列的值傳回給a的步驟注釋了,同時在merge_sort( int a, int n )
函式中,我們每次迴圈都merge_pass
兩次:
while( length
< n )
這樣可以確保我們將序列歸併排序後的資料一定是儲存在陣列a中。
如果每次都在每次歸併的時候,加上那句for語句,那麼也就是每次都將排序結果傳回給陣列a,也能確保排序後的值在陣列a中。但是這樣處理的過程和原先的過程是有卻別的,現在的過程是:
歸併呼叫merge將相鄰的子表歸併時,必須對錶的特殊情況進行特殊處理:
若子表個數為奇數,則最後乙個子表無須和其他子表歸併(即本趟處理輪空):若子表個數為偶數,則要注意到最後一對子表中後乙個子表區間的上限為n-1。
示意圖如下:
從n=7和n=9的兩幅圖就可以看出,乙個是每次所有元素都進行歸併了,乙個是將殘留項(最後一項的長度小於length)保留,直到最後再歸併。
所以也可以將**改寫如下:
#include "stdio.h"
#include "stdlib.h"
void merge( int a, int tmpa, int l, int r, int rightend )
while( l <= leftend )
tmpa[tmp++] = a[l++]; /* 直接複製左邊剩下的 */
while( r <= rightend )
tmpa[tmp++] = a[r++]; /* 直接複製右邊剩下的 */
for( i = 0; i < numelements; i++, rightend -- )
a[rightend] = tmpa[rightend]; /*將有序的tmpa複製回a */
}/* length = 當前有序子列的長度*/
void merge_pass( int a, int tmpa, int n, int length )
void merge_sort( int a, int n )
free( tmpa );
}else
printf( "空間不足" );
}int main()
; //int a[10]= ;
merge_sort(a,10);
for(int i = 0;i<10;i++)
printf("%d ",a[i]);
return
0;}
歸併排序 非遞迴實現
1 歸併,非遞迴實現 1 段長度倍增,1,2,3,4,8,n 當要合併的單段長 2 以兩段的長度作為每次偏移的長度 每次合併是兩個單段一組進行合併,因此要不斷地偏移兩個單段的長度。5 3 5 2 8 7 2 3 5 7 8 include include include using namespac...
歸併排序(遞迴 非遞迴 自然歸併排序)
演算法思想 歸併排序是分治法的典型應用,其思想是不斷地將兩個有序的陣列合併為乙個有序陣列。遞迴實現 include void merge int a,int left,int m,int right void mergesortaux int a,int left,int right void me...
遞迴和非遞迴實現歸併排序
1 遞迴實現歸併排序 遞迴排序其實也是利用了完全二叉樹的結構形式,所以時間複雜度和logn掛鉤的。具體遞迴版的歸併排序呢,看下面的 public class ms print s mergesort s system.out.println 排序後的陣列 print s public static ...