歸并排序是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合并,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合并成一個有序表,稱為二路歸并。
基本思想:
歸并(Merge)排序法是將兩個(或兩個以上)有序表合并成一個新的有序表,即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合并為整體有序序列。
初始關鍵字 49 38 65 97 76 13 27
第一趟歸并 (38 49) (65 97) (13 76) 27
第二趟歸并 (38 49 65 97) (13 27 76)
第三趟歸并 (13 27 38 49 65 76 97)
將待排序序列R[0...n-1]看成是n個長度為1的有序序列,將相鄰的有序表成對歸并,得到n/2個長度為2的有序表;將這些有序序列再次歸并,得到n/4個長度為4的有序序列;如此反復進行下去,最后得到一個長度為n的有序序列。
綜上可知,歸并排序其實要做兩件事:
(1)“分解”——將序列每次折半劃分。
(2)“合并”——將劃分后的序列段兩兩合并后排序。
我們先來考慮第二步,如何合并?
在每次合并過程中,都是對兩個有序的序列段進行合并,然后排序。這兩個有序序列段分別為 R[low, mid] 和 R[mid+1, high]。先將他們合并到一個局部的暫存數(shù)組R2中,帶合并完成后再將R2復制回R中。
為了方便描述,我們稱 R[low, mid] 第一段,R[mid+1, high] 為第二段。每次從兩個段中取出一個記錄進行關鍵字的比較,將較小者放入R2中。最后將各段中余下的部分直接復制到R2中。經(jīng)過這樣的過程,R2已經(jīng)是一個有序的序列,再將其復制回R中,一次合并排序就完成了。
// 合并代碼
void Merge(int sourceArr[], int tempArr[], int startIndex, int midIndex, int endIndex) {
int i = startIndex; // i是第一段序列的下標
int j = midIndex + 1; // j是第二段序列的下標
int k = startIndex; // k是臨時存放合并序列的下標
while(i <= midIndex && j <= endIndex) {
// 判斷第一段和第二段取出的數(shù)哪個更小,將其存入合并序列,并繼續(xù)向下掃描
if(sourceArr[i] >= sourceArr[j]) {
tempArr[k++] = sourceArr[j++];
} else {
tempArr[k++] = sourceArr[i++];
}
}
// 若第一段序列還沒掃描完,將其全部復制到合并序列
while(i <= midIndex) {
tempArr[k++] = sourceArr[i++];
}
// 若第二段序列還沒掃描完,將其全部復制到合并序列
while(j <= endIndex) {
tempArr[k++] = sourceArr[j++];
}
// 將合并序列復制到原始序列中
for(i = startIndex; i <= endIndex; i++) {
sourceArr[i] = tempArr[i];
}
}
掌握了合并的方法,接下來,讓我們來了解如何分解。
在某趟歸并中,設各子表的長度為gap,則歸并前R[0...n-1]中共有n/gap個有序的子表:R[0...gap-1], R[gap...2gap-1], ... , R[(n/gap)gap ... n-1]。
調(diào)用Merge將相鄰的子表歸并時,必須對表的特殊情況進行特殊處理。
若子表個數(shù)為奇數(shù),則最后一個子表無須和其他子表歸并(即本趟處理輪空):若子表個數(shù)為偶數(shù),則要注意到最后一對子表中后一個子表區(qū)間的上限為n-1。
void MergePass(int sourceArr[], int tempArr[], int gap, int length) {
int low = 0;
// 歸并gap長度的兩個相鄰子表
for (low = 0; low + 2 * gap - 1 < length; low = low + 2 * gap) {
Merge(sourceArr, tempArr, low, low + gap - 1, low + 2 * gap - 1);
}
// 余下兩個子表,后者長度小于gap
if (low + gap - 1 < length) {
Merge(sourceArr, tempArr, low, low + gap - 1, length - 1);
}
}
void MergeSort(int sourceArr[], int tempArr[], int length) {
for (int gap = 1; gap < length; gap = 2 * gap) {
MergePass(sourceArr, tempArr, gap, length);
}
}
算法的實現(xiàn)(非遞歸實現(xiàn)版本):
// 輸出數(shù)組內(nèi)容
void print(int array[], int length) {
for (int j = 0; j < length; j++) {
printf(" %d ", array[j]);
}
printf("\n");
}
// 合并-將劃分后的序列段兩兩合并后排序
void Merge(int sourceArr[], int tempArr[], int startIndex, int midIndex, int endIndex) {
int i = startIndex; // i是第一段序列的下標
int j = midIndex + 1; // j是第二段序列的下標
int k = startIndex; // k是臨時存放合并序列的下標
while(i <= midIndex && j <= endIndex) {
// 判斷第一段和第二段取出的數(shù)哪個更小,將其存入合并序列,并繼續(xù)向下掃描
if(sourceArr[i] >= sourceArr[j]) {
tempArr[k++] = sourceArr[j++];
} else {
tempArr[k++] = sourceArr[i++];
}
}
// 若第一段序列還沒掃描完,將其全部復制到合并序列
while(i <= midIndex) {
tempArr[k++] = sourceArr[i++];
}
// 若第二段序列還沒掃描完,將其全部復制到合并序列
while(j <= endIndex) {
tempArr[k++] = sourceArr[j++];
}
// 將合并序列復制到原始序列中
for(i = startIndex; i <= endIndex; i++) {
sourceArr[i] = tempArr[i];
}
}
void MergeSort(int sourceArr[], int tempArr[], int length) {
for (int gap = 1, low = 0; gap < length; gap = 2 * gap) {
// 歸并gap長度的兩個相鄰子表
for (low = 0; low + 2 * gap - 1 < length; low = low + 2 * gap) {
Merge(sourceArr, tempArr, low, low + gap - 1, low + 2 * gap - 1);
}
// 余下兩個子表,后者長度小于gap
if (low + gap - 1 < length) {
Merge(sourceArr, tempArr, low, low + gap - 1, length - 1);
}
}
}
int main(int argc, const char * argv[]) {
int sourceArr[7] = { 49,38,65,97,76,13,27 };
int tempArr[7];
MergeSort(sourceArr, tempArr, 7);
print(sourceArr, 7);
return 0;
}
算法的實現(xiàn)(遞歸實現(xiàn)的版本):
// 輸出數(shù)組內(nèi)容
void print(int array[], int length) {
for (int j = 0; j < length; j++) {
printf(" %d ", array[j]);
}
printf("\n");
}
// 合并-將劃分后的序列段兩兩合并后排序
void Merge(int sourceArr[], int tempArr[], int startIndex, int midIndex, int endIndex) {
int i = startIndex; // i是第一段序列的下標
int j = midIndex + 1; // j是第二段序列的下標
int k = startIndex; // k是臨時存放合并序列的下標
while(i <= midIndex && j <= endIndex) {
// 判斷第一段和第二段取出的數(shù)哪個更小,將其存入合并序列,并繼續(xù)向下掃描
if(sourceArr[i] >= sourceArr[j]) {
tempArr[k++] = sourceArr[j++];
} else {
tempArr[k++] = sourceArr[i++];
}
}
// 若第一段序列還沒掃描完,將其全部復制到合并序列
while(i <= midIndex) {
tempArr[k++] = sourceArr[i++];
}
// 若第二段序列還沒掃描完,將其全部復制到合并序列
while(j <= endIndex) {
tempArr[k++] = sourceArr[j++];
}
// 將合并序列復制到原始序列中
for(i = startIndex; i <= endIndex; i++) {
sourceArr[i] = tempArr[i];
}
}
// 二路歸并排序(Merge Sort)
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex) {
int midIndex;
if(startIndex < endIndex) { // 是if,不是while,且不含等號,否則死循環(huán)
midIndex = (startIndex + endIndex) / 2;
MergeSort(sourceArr, tempArr, startIndex, midIndex);
MergeSort(sourceArr, tempArr, midIndex+1, endIndex);
Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);
}
}
int main(int argc, const char * argv[]) {
int sourceArr[7] = { 49,38,65,97,76,13,27 };
int tempArr[7];
MergeSort(sourceArr, tempArr, 0, 6);
print(sourceArr, 7);
return 0;
}
總結(jié)
若從空間復雜度來考慮:首選堆排序,其次是快速排序,最后是歸并排序。
若從穩(wěn)定性來考慮,應選取歸并排序,因為堆排序和快速排序都是不穩(wěn)定的。
若從平均情況下的排序速度考慮,應該選擇快速排序。