此博客用于个人学习,来源于算法的书籍和网上的资料,对知识点进行一个整理。
1. 概述:
要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。
2. 算法:
实现归并的一种直截了当的方法是将两个不同的有序数组归并到第三个数组中,两个数组的元素应该都实现了 Comparable 接口。但是,当归并将一个大数组排序的时候,我们需要进行很多次归并,因此不能采取每次归并时都创建一个新数组来存储排序结果的方式。更希望有一种原地归并的方法,这样就可以先将前半部分排序,再将后半部分排序,然后在数组中移动元素而不需要使用额外的空间。
该方法先将所有元素复制到 aux[] 中,然后再归并回 a[] 中。方法在归并时(第二个 for 循环)进行了4个条件判断:左半边用尽(取右半边的元素),右半边用尽(取左半边的元素),右半边的当前元素小于左半边的当前元素(取右半边的元素)以及右半边的当前元素大于等于左半边的当前元素(取左半边的元素)。
3. 代码实现:
- 自顶向下的归并排序:(递归)
/**
* 自顶向下的归并排序
*/
public class Merge {
//归并所需的辅助数组
private static Comparable[] aux;
public static void sort(Comparable[] a){
//一次性分配空间
aux = new Comparable[a.length];
sort(a,0,a.length-1);
}
//将数组a[lo...hi]排序
private static void sort(Comparable[] 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);
}
//将 a[lo...mid] 和 a[mid+1...hi]归并
public static void merge(Comparable[] a,int lo,int mid,int hi){
int i = lo,j = mid + 1;
//将数组a[k]复制到aux[x]上
for (int k = lo;k <= hi;k++){
aux[k] = a[k];
}
//归并回到a[lo...hi]
for (int k = lo;k <= hi;k++){
if (i > mid){
a[k] = aux[j++];
}else if (j > hi){
a[k] = aux[i++];
}else if (less(aux[j],aux[i])){
a[k] = aux[j++];
}else {
a[k] = aux[i++];
}
}
}
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w) < 0;
}
}
- 自底向上的归并排序:(非递归)
/**
* 自底向上的归并排序
*/
public class MergeBU {
private static Comparable[] aux;
public static void sort(Comparable[] a){
int N = a.length;
aux = new Comparable[a.length];
for (int sz = 1;sz < N;sz *= 2){
for (int lo = 0;lo < N - sz;lo += sz + sz){
merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1,N-1));
}
}
}
//将 a[lo...mid] 和 a[mid+1...hi]归并
public static void merge(Comparable[] a,int lo,int mid,int hi){
int i = lo,j = mid + 1;
//将数组a[k]复制到aux[x]上
for (int k = lo;k <= hi;k++){
aux[k] = a[k];
}
//归并回到a[lo...hi]
for (int k = lo;k <= hi;k++){
if (i > mid){
a[k] = aux[j++];
}else if (j > hi){
a[k] = aux[i++];
}else if (less(aux[j],aux[i])){
a[k] = aux[j++];
}else {
a[k] = aux[i++];
}
}
}
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w) < 0;
}
}
4. 特点:
- 优点:
- 归并排序的效率达到了巅峰:时间复杂度为 O(nlogn),这是基于比较的排序算法所能达到的最高境界。
- 归并排序是一种稳定的算法(即在排序过程中大小相同的元素能够保持排序前的顺序,3212升序排序结果是1223,排序前后两个2的顺序不变),这一点在某些场景下至关重要。
- 归并排序是最常用的外部排序方法(当待排序的记录放在外存上,内存装不下全部数据时,归并排序仍然适用,当然归并排序同样适用于内部排序)。
- 缺点:归并排序需要 O(n) 的辅助空间,而与之效率相同的快排和堆排分别需要 O(logn) 和 O(1) 的辅助空间,在同类算法中归并排序的空间复杂度略高。
5. 算法分析:
-
时间复杂度:假设处理的数据规模大小为 N,运行时间设为:T(N)
① 当把 N 分为两半时,那么处理大小为 N/2 子数组花费时间为:T(N/2)
② 合并花费时间与数据规模成正比:N
所以处理规模大小为N的数据所需要花费两个大小为 N/2 的子数组加上合并花费的时间
即:T(N) = 2T(N/2) + N,假设 N 是2的幂,计算可得复杂度为 O(nlogn)。
-
空间复杂度:O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序。
-
稳定性:归并排序是稳定的排序算法,if (less(aux[j],aux[i])){a[k] = aux[j++];} 这行代码可以保证当左右两部分的值相等的时候,先复制左边的值,这样可以保证值相等的时候两个元素的相对位置不变。