归并是指将两个较小的有序数组合并为一个大的有序数组, 利用这个方法的排序就叫做归并排序.
归并排序体现了分而治之的思想, 也就是将大问题分解为小问题, 解决完小问题就解决了大问题: 我们将无序数组分为两个小数组, 两个小数组在继续细分, 这样分到每一个小数组都只有一个元素, 那么自然这个小数组就是有序的了, 在一一合并, 这样就组成了一个大数组
总体来说还是很好理解的, 首先我们先写合并部分的代码:
1 public static void merge(int[] a, int lo, int mid, int hi) { //归并操作 2 int i = lo, j = mid + 1; 3 for(int k = lo; k <= hi; k++) { 4 aux[k] = a[k]; 5 } 6 for(int k = lo; k <= hi; k++) { 7 if(i > mid) // 左边元素取尽 8 a[k] = aux[j++]; 9 else if(j > hi) // 右边元素取尽 10 a[k] = aux[i++]; 11 else if(aux[j] > aux[i]) 12 a[k] = aux[i++]; 13 else 14 a[k] = aux[j++]; 15 } 16 }
merge()函数的意思是将lo到hi这一段的数组用mid分为两部分, 然后用每次讲这两个小数组的最前面的数中最小的那个加到大数组中, 当两个数组都取尽时, 合并完成, 同时保证了大数组的有序性
我们发现, 合并过程有一个不好的地方, 就是需要临时定义一个数组临时存储数据, 由于这个merge()函数会调用很多次, 所以我们就定义在了函数的外面, 避免多次定义数组影响性能.
接下来我们些排序的主体, 我们可以想到有递归和递推两种方式, 在书上把这两个称为自顶向下的归并排序和自底向上的归并排序, 我们先写递归的归并(自顶向下):
1 public static void top_down_sort(int[] a, int lo, int hi) { //自顶向下的归并(递归) 2 if(hi <= lo) 3 return; 4 int mid = lo + (hi-lo)/2; 5 top_down_sort(a ,lo, mid); 6 top_down_sort(a, mid+1, hi); 7 merge(a, lo, mid, hi); 8 }
可以很清楚的理解代码吧数组分成两部分, 并递归这两个部分, hi<=lo时, 小数组中只有一个元素, 递归停止
接下来, 是自底向上(递推)的代码:
1 public static void down_top_sort(int[] a) { //自底向上的归并(递推) 2 int N = a.length; 3 for(int sz = 1; sz < N; sz++) //sz为子数组大小 4 for(int lo = 0; lo < N-sz; lo += sz*2) // lo为子数组索引 5 merge(a, lo, lo+sz-1, Math.min(lo+sz*2-1, N-1)); 6 }
这个就是直接从大小为1的小数组开始, 然后两两合并, 直到组成大数组
归并排序的速度很快,它只要使用lg(n!)~nlgn次比较就能完成排序, 时间复杂度为O(nlogn),但其空间复杂度为O(n), 因为我们需要定义新数组来临时存储数组变量