归并,将两个有序的数组归并成一个更大的有序数组。
要将一个数组排序,先(递归地)将数组分成两半分别排序,然后将结果归并起来。
归并排序优点:保证将任意长度为的数组排序所需要时间与
成正比。
归并排序缺点:所需的额外空间和成正比。
原地归并
归并很简单的做法是建立第三个数组,将两个数组合并到第三个数组中。这种做法在我们做大数组归并排序的时候并不可取,因为会做多次归并,导致申请过多的额外空间。所以最好就是我们能够在原有数组上进行归并,这样就不会有过多的存储空间申请。
原地归并的抽象方法:
/**
* 将input[start...mid]和input[mid+1...end]归并
*
*/
private static void merge(Comparable[] input, int start, int mid, int end) {
int startIndex = start;
int endIndex = mid + 1;
for (int i = 0; i < end + 1; i++) {
tempInput[i] = input[i];
}
for (int i = start; i < end + 1; i++) {
if (startIndex > mid) {
input[i] = tempInput[endIndex++];
} else if (endIndex > end) {
input[i] = tempInput[startIndex++];
} else if (less(input[startIndex], input[endIndex])) {
input[i] = tempInput[startIndex++];
} else {
input[i] = tempInput[endIndex++];
}
}
}
自顶向下的归并排序
自顶向下的归并排序算法是典型的采用高效算法设计中分治思想的例子。递归将数组平分,直到比较的两个数组都是一个,然后自底向上进行归并。
自顶向下的归并排序:
private static Comparable[] tempInput;
/**
* 归并排序
*
*/
public static void mergeSort(Comparable[] input) {
tempInput = new Comparable[input.length];
mergeSort(input, 0, input.length);
}
/**
* 分治的思想,采用递归
*
*/
private static void mergeSort(Comparable[] input, int start, int end) {
if (start >= end) {
return;
}
int mid = (start + end) / 2;
mergeSort(input, start, mid);
mergeSort(input, mid + 1, end);
merge(input, start, mid, end);
}
例子:
自底向上的归并排序
自底向上的归并排序与自顶向下不同的是没有递归分组,采用循环遍历的方式分组。先是两两分组归并(每个元素想像成一个数组),接着四四分组归并(每两个元素组成一个数组),接下来是八八分组归并(每四个元素组成一个数组),以此类推。在循环遍历过程中,可能会出现最后一次归并的第二个数组的大小小于第一个数组,但这对merge()方法不是问题。
自底向上的归并排序:
private static void mergeSortB(Comparable[] input) {
int length = input.length;
for (int i = 1; i < length; i += i) {
for (int j = 0; j < length; j += 2 * i) {
merge(input, j, j + i - 1, Math.min(j + i + i - 1, length - 1));
}
}
}
另外,归并排序需要处理很多小型数组的排序,对于小型的数组的排序完全可以用插入排序或选择排序等简单的排序,很可能比归并排序的速度更快。
参考自《算法》