此博客用于个人学习,来源于算法的书籍,对知识点进行一个整理。
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,是一种典型的分而治之思想的算法应用。
1. 概述:
要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。
2. 原地归并的抽象方法:
实现归并的一种直截了当的方法是将两个不同的有序数组归并到第三个数组中,两个数组的元素应该都实现了 Comparable 接口。但是,当归并将一个大数组排序的时候,我们需要进行很多次归并,因此不能采取每次归并时都创建一个新数组来存储排序结果的方式。更希望有一种原地归并的方法,这样就可以先将前半部分排序,再将后半部分排序,然后在数组中移动元素而不需要使用额外的空间。
//将 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++];
}
}
}
该方法先将所有元素复制到 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;
}
}
要对子数组 a[lo…hi] 进行排序,先将它分为 a[lo…mid] 和 a[mid+1…hi] 两部分,分别通过递归调用将它们单独排序,最后将有序的子数组归并为最终的排序结果。
4. 自底向上的归并排序:
上一种的归并排序是基于递归实现的,尽管我们考虑的问题是归并两个大数组,实际上我们归并的数组大多数都比较小。实现归并排序的另一种方法是先归并那些微型数组,然后再成对归并得到的子数组,如此这般,直到我们将整个数组归并到一起。这种实现方法比标准递归方法所想要的代码量更少。首先我们进行的是两两归并(把每个元素想象成一个大小为1的数组),然后是四四归并(将两个大小为2的数组归并成一个有4个元素的数组),然后是八八的归并,一直下去。
/**
* 自底向上的归并排序
*/
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;
}
}
自底向上的归并排序回多次遍历整个数组,根据子数组的大小进行两两归并。子数组的大小 sz 的初始值为1,每次加倍。最后一个子数组的大小只有在数组大小是 sz 的偶数倍的时候才会等于 sz。这种归并排序比较适合用链表组织的数据。