歸併排序:要將一個數組排序,可以先(遞歸地)將它分成兩半分別排序,然後將結果歸併起來。歸併排序最吸引人的性質是它能夠保證將任意長度爲N的數組排序所需時間和NlogN成正比;它的主要缺點則是它所需的額外空間和N成正比。
基本思想:
- 將數組一分爲(Divide array into two halves)
- 對每部分進行遞歸式地排序(Recursively sort each half)
- 合併兩個部分(Merge two halves)
演示:
1. 給出原數組a[],該數組的lo到mid,mid+1到hi的子數組是各自有序的。
2. 將數組複製到輔助數組(auxiliary array)中,給兩部分的首元素分別以i和j的下標,給原數組首元素以k的下標
3. 比較i下標和j下標的元素,將較小值賦到k下標位置的元素內,然後對k和賦值的下標進行遞增;
該演示裏j下標的元素比較小,於是將A賦到k的位置裏,再對k和j遞增,即j+1, k+1
4. 重複上述過程,直到比較完全部元素。
public class Merge {
private static int[] aux;
public static void sort(int[] a){
aux = new int[a.length];
sort(a,0,a.length-1);
}
private static void sort(int[] a,int lo,int hi){
if(hi <=lo) return;
int mid = lo + (hi-lo)/2;
sort(a,lo,mid);//將左半邊排序
sort(a,mid+1,hi);//將右半邊排序
merge(a,lo,mid,hi);//歸併結果
}
public static void merge(int[] a,int lo,int mid,int hi){
//將a[lo..mid]和a[mid+1..hi]歸併
int i = lo, j = mid+1;
for(int k= lo;k <= hi;k++)//將a[lo..hi]複製到aux[lo..hi]
aux[k] = a[k];
for(int k = lo;k <= hi;k++){
if (i >mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (aux[j] < aux[i]) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
}
性能分析:
算法複雜度爲N*log(N)
優化:
問題:歸併排序需要根據數組大小N開闢額外的內存
原地算法(in-place Algorithm):佔用額外空間小於等於c log(N)的排序算法。
插入排序、選擇排序、希爾排序都屬於原地算法。歸併排序不屬於原地算法。Wiki參考
Kronrod在1969年發明了原地歸併排序(in-place merge),不過看起來好像不是那麼有用(Challenge for the bored)