歸併排序
歸併思想:將兩個有序的數組歸併成一個更大的有序數組。
優點:保證將任意長度N的數組排序所需時間和NlogN成正比。
缺點:所需的額外空間和N成正比。
1 原地歸併的抽象方法
所謂原地歸併,既是將連個已經有序的子序列合併爲一個序列。
/*
*需要額外的數組大小的空間
*主要是對四種情況的比較:
*1.左半邊用盡
*2.右半邊用盡
*3.右半邊當前元素小於當前元素
*4.右半邊當前元素大於當前元素
*/
void Merge(int *a, int low, int mid, int high){
//將a中的元素都複製到aux中去。
for (int k = low; k <= high; ++k)
aux[k] = a[k];
//再將aux中的元素排序到a中
int i = low;
int j = mid + 1;
for (int k = low; k <= high; ++k){
if (i > mid)
a[k] = aux[j++];
else if (j > high)
a[k] = aux[i++];
else if (aux[j] < aux[i]) //這裏一開始寫成(a[j] < a[i]),調試了1個小時,打臉!!!
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
2 自頂向下的歸併排序
對於N個數據來講,劃分爲n層,對於0~n-1之間的任意k,自頂向下第k層有2的k次方個數組,每個數組長度爲2的(n-k)次方,歸併最多需要2的(n-k)次比較,因此,每層的比較次數爲2的n次方,n層總共爲n*2的n次 = NlgN。
下面給出測試例子:
#include <iostream>
using namespace std;
typedef void(*Fp)(int *a, int count);
//根據<<Effective C++>> 條款02 : 儘量使用const,enum,inline替換#define
const int Count = 10;
int aux[Count];
//原地歸併的抽象方法
void Merge(int *a, int low, int mid, int high);
//自頂向下的歸併排序
void merge_sort(int *a, int count);
void _merge_sort(int *a, int low, int high);
int main()
{
int a[] = { 23, 3, 45, 123, 2, 1, 342, 52, 452, 22 };
//排序前:
cout << "排序前:" << endl;
for (int i = 0; i < Count; ++i)
cout << a[i] << " ";
cout << endl;
Fp fp = merge_sort;
fp(a, Count);
//排序後:
cout << "排序後:" << endl;
for (int i = 0; i < Count; ++i)
cout << a[i] << " ";
cout << endl;
return 0;
}
/*
*需要額外的數組大小的空間
*主要是對四種情況的比較:
*1.左半邊用盡
*2.右半邊用盡
*3.右半邊當前元素小於當前元素
*4.右半邊當前元素大於當前元素
*/
void Merge(int *a, int low, int mid, int high){
//將a中的元素都複製到aux中去。
for (int k = low; k <= high; ++k)
aux[k] = a[k];
//再將aux中的元素排序到a中
int i = low;
int j = mid + 1;
for (int k = low; k <= high; ++k){
if (i > mid)
a[k] = aux[j++];
else if (j > high)
a[k] = aux[i++];
else if (aux[j] < aux[i]) //這裏一開始寫成(a[j] < a[i]),調試了1個小時,打臉!!!
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
void merge_sort(int *a, int count){
_merge_sort(a, 0, count - 1);
}
void _merge_sort(int *a, int low, int high){
//首先判斷是否只有1個元素
if (high <= low)
return;
int mid = low + (high - low) / 2;
//歸併的排列左半邊
_merge_sort(a, low, mid);
//歸併的排列右半邊
_merge_sort(a, mid + 1, high);
//將兩邊合併
Merge(a, low, mid, high);
}
這種排序是高效算法設計中分治思想最典型的一個例子。
下面給出幾種性質:
1)對於長度爲N的任意數組,自頂向下的歸併排序需要1/2NlgN至NlgN次比較。
2)對於長度爲N的任意數組,自頂向下的歸併排序最多需要訪問數組6NlgN次。
對於自頂向下的歸併排序有以下幾點可以優化的地方:
1)對於小規模子數組使用插入排序(比如長度小於15,一般可以將時間縮短10%到15%)。
2)測試數組是否已經有序。(如果a[mid]小於等於a[mid+1],就認爲數組已經是有序的並跳過merge()方法)。
3)不將元素複製到輔助數組(時間、空間不行)。
3 自底向上的歸併排序
先上源碼:
void mergeBU_sort(int *a, int count){
int end = 0;
for (int sz = 1; sz < Count; sz = sz + sz){ //sz表示子數組的大小
for (int lo = 0; lo < Count - sz; lo += sz + sz){ //lo:子數組的索引
end = (lo + sz + sz - 1) < (Count - 1) ? (lo + sz + sz - 1) : (Count - 1);
Merge(a, lo, lo + sz - 1, end);
}
}
}
思想:首先進行兩兩歸併,然後四四歸併,然後八八歸併。。。
對於長度爲N的任意數組,自底向上的歸併排序需要1/2NlgN至NlgN次比較,最多訪問數組6NlgN次。
優勢:自底向上的歸併排序比較適合用鏈表組織的數據。