归并排序:要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。归并排序最吸引人的性质是它能够保证将任意长度为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)