归并排序
一.思想
要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。归并就是将两个有序的数组归并成一个更大的有序数组。
二.原地归并的抽象方法
实现归并一种直截了当的方法就是将两个不同的有序数组归并到第三个数组中,但是,当用归并将一个大数组排序时,需要进行很多次归并,如果每次在归并都创建一个新数组来存储排序结果会带来问题。所以我们用在归并算法类中定义一个辅助数组aux来临时存储排序数组的值,我们先把数组的值复制到aux数组中,然后再把两个有序的数组归并回原来的数组中,代码如下:
private static void merge(Comparable[] a,int lo,int mid,int hi){
int i = lo;
int j = mid + 1;
for (int k = lo; k <= hi; k++) {
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 (less(aux[j],aux[i])){
a[k] = aux[j++];
}else {
a[k] = aux[i++];
}
}
}
该方法先将所有元素复制到aux数组中,然后再归并回a[ ],然后进行了4个条件判断:
- 左半边用尽 -> 取右半边元素
- 右半边用尽 -> 取左半边元素
- 右半边的当前元素小于左半边的当前元素->取右半边元素
- 左半边的当前元素小于等于右半边的当前元素->取左半边元素
三.自顶向下的归并排序
基于原地归并的抽象实现了归并排序,这也是应用高效算法设计分治思想的最典型的一个例子,要对a[lo…hi]进行排序,先将他们分为a[lo…mid]和a[mid…hi]两部分,分别通过递归调用将它们单独排序,最后将有序的子数组归并为最终的排序结果,代码如下:
private static Comparable[] aux; //归并所需要的辅助数组
public static void sort(Comparable[] a){
aux = new Comparable[a.length];
sort(a, 0,a.length-1);
}
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);
};
四.自底向上的归并排序
递归实现的归并排序是算法设计中分治思想的典型应用。我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。但是在实际应用中我们归并的数组大多数都非常小,实现归并的另一种方法就是先将那些微型数组归并,然后再成对归并得到的子数组,反复执行。直到我们将整个数组归并在一起。
我们把每个元素想象成一个大小为1的数组,进行两两归并,归并之后我们把上一次归并已经排序好的数组即大小为2的数组归并成一个四个元素的数组,即四四归并,然后是八八归并,一直下去。
public class MergeBU {
private static Comparable[] aux; //归并所需要的辅助数组
public static void sort(Comparable[] a){
int N = a.length;
aux = new Comparable[N];
for (int sz = 1; sz < N; sz=sz+sz) {//sz子数组的大小
for (int lo = 0; lo < N-sz; lo=lo+2*sz) {//子数组索引
merge(a, lo, lo+sz-1, Math.min(lo+2*sz-1,N-1));
}
}
}
/**
* 比较大小,如果v小于w就返回true
* @param v
* @param w
* @return
*/
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w) < 0;
}
public static void merge(Comparable[] a,int lo,int mid,int hi){
int i = lo;
int j = mid+1;
for (int k = lo; k <= hi; k++) {
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 (less(aux[j],aux[i])){
a[k] = aux[j++];
}else {
a[k] = aux[i++];
}
}
}
}
自底向上的归并排序会多次遍历整个数组,根据子数组大小进行排序。子数组的大小sz的初始化值为1,每次加倍。最后一个子数组的大小只有在数组大小是sz的偶数倍的时候才会等于sz(否则它只会比sz小)