1. 归并排序
1.1 排序原理
- 采用分治思想,一般用递归实现
- 将一个无序数组从中间分成两个无序数组,然后对两个无序数组排序,排序之后再将两个有序数组合并为一个有序数组;
1.2 性能分析
1.2.1 执行效率
它的效率和原始数组的元素顺序无关,效率稳定;最好,最坏,平均复杂度都一样;
假设n个元素的排序时间为T(n),那么两个子数组的排序时间就是2*T(n/2),合并两个子数组的时间是O(n);
则T(n)= 2*T(n/2)+ n ;继续分解如下
T(n) = 2*T(n/2) + n = 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n = 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n = 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n ...... = 2^k * T(n/2^k) + k * n ......
T(n/2^k)最小时等于T(1);即 n/2^k = 1 时,k = log2n, 带入上述公式,
T(n)=Cn+nlog2n,
用大O表示法表示为T(n)=O( n log n);
1.2.2 空间复杂度
每次合并数组时需要申请额外空间,但是每次合并完后都会释放内存,最多申请N个元素的空间,所以空间复杂度是O(n),所以不是原地排序算法。
1.2.3 算法稳定性
在合并两个较小的数组时,可以元素相等时优先将前面的元素拷贝到临时数组,这样就可以保证稳定性。
1.3 代码实现
// 递归调用函数 private static void mergeSortInternally(int[] a, int start, int end) { // 递归终止条件 if (start >= end) return; // 取start到end之间的中间位置mid,防止(start+end)的和超过int类型最大值 int mid = start + (end - start)/2; // 分治递归 mergeSortInternally(a, start, mid); mergeSortInternally(a, mid+1, end); // 将a[start...mid]和A[mid+1...end]合并为A[start...end] merge(a, start, mid, end); } private static void merge(int[] a, int p, int q, int r) { int i = p; int j = q+1; int k = 0; // 初始化变量i, j, k int[] tmp = new int[r-p+1]; // 申请一个大小跟a[p...r]一样的临时数组 while (i<=q && j<=r) { if (a[i] <= a[j]) { tmp[k++] = a[i++]; } else { tmp[k++] = a[j++]; } // 当一个数组中的元素全部拷贝到tmp中时,就将另一个数组剩余的元素也全部拷贝到tmp中 if (i == (q+1)){ while(j<=r){ tmp[k++] = a[j++]; } } if (j == (r+1)){ while(i<=q){ tmp[k++] = a[i++]; } } } // // 判断哪个子数组中有剩余的数据 // int start = i; // int end = q; // if (j <= r) { // start = j; // end = r; // } // // // 将剩余的数据拷贝到临时数组tmp // while (start <= end) { // tmp[k++] = a[start++]; // } // 将tmp中的数组拷贝回a[p...r] for (i = 0; i <= r-p; ++i) { a[p+i] = tmp[i]; } }