归并排序是一个O(n log n)排序算法,采取了分治的思想。将已有序的子序列合并,得到完全有序的序列,即使得每个子序列有序,再使得每个子序列段有序,最后整个序列有序。图示是自顶向下的归并排序过程,采取递归思想
归并排序合并过程是算法的关键过程,需要生成一个辅助数组,大小与需要合并的两部分子序列大小之和,临时装载需要排序的元素,用k指示排序后元素所在的位置,i是左序列标定点,j是右序列标定点;如果i指示元素小于j指示元素则将i指示元素填入k的位置上,k++,i++, j不动否则将j指示的元素填入k的位置上,k++, j++,i不动,还要考虑i超过中间标定点mid,此时应当将j指示及之后的元素依次赋给k及之后位置的元素,并且j已经超过序列右边界,此时应当将i指示及之后的元素依次赋给k及之后位置的元素。
以下是归并排序实现过程
//普通归并排序
public class MergeSort {
//归并排序函数,调用私有递归归并函数
public void mergeSort(int[] arr, int n) {
//传入整个数组, 从arr[0...n-1]进行归并
mergeSort(arr, 0, n - 1);
}
//归并排序函数
private void mergeSort(int[] arr, int l, int r) {
//如果左标定点大于等于右标定点,递归终止
if (l >= r)
return;
//求取中间标定点
int mid = (l + r) / 2;
//左区间arr[l...mid]归并排序
mergeSort(arr, l, mid);
//右区间arr[l...mid]归并排序
mergeSort(arr, mid + 1, r);
//归并操作
merge(arr, l, mid, r);
}
//归并函数
private void merge(int[] arr, int l, int mid, int r) {
//产生一个与需要归并数组一样大小的数组
int[] aux = new int[r - l + 1];
//将原数组原位置相应位置元素赋值给辅助数组
for (int i = l; i <= r; i++)
aux[i - l] = arr[i];
//i, j 表示 k标定元素排序后所处位置
int i = l, j = mid + 1;
for (int k = l; k <= r; k++) {
//当左标定i点超过中点mid时,让大于等于j标定点所对应的元素直接赋给arr对于k及k之后的元素
if (i > mid) {
arr[k] = aux[j - l];
j++;
}
//当左标定j点超过右边界r时,让大于等于i标定点所对应的元素直接赋给arr对于k及k之后的元素
else if (j > r) {
arr[k] = aux[i - l];
i++;
}
//当左标定点对应的元素小于右标定点元素,将i对应元素赋给arr k位置上元素,维护i,挪动一位置
else if (aux[i - l] < aux[j - l]) {
arr[k] = aux[i - l];
i++;
}
//当右标定点对应的元素小于等于左标定点元素,将j对应元素赋给arr k位置上元素,维护j,往前挪动
else {
arr[k] = aux[j - l];
j++;
}
}
}
}
针对100000数据量的随机数组、大量重复元素的数组、近乎有序的数组进行测试,以下是测试代码
import cn.zjut.util.SortTestUtil;
public class Main {
public static void main(String[] args){
int n = 100000;
System.out.println("Test for Random Array, size = " + n + ", random range [0, " + n + ']');
int[] arr1 = SortTestUtil.generateRandomArray(n, 0, n);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
System.out.println("--------------------------------");
System.out.println("Test for Random Array, size = " + n + ", random range [0, " + 10 + ']');
arr1 = SortTestUtil.generateRandomArray(n, 0, 10);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
System.out.println("--------------------------------");
int swapTime = 100;
System.out.println("Test for Random Nearly Ordered Array, size = " + n + ", swap time = " + swapTime);
arr1 = SortTestUtil.generateNearlyOrderedArray(n, swapTime);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
}
}
以下是测试结果
由结果可知针对10万数据量归并排序效果依然很好,对于有大量重复元素的数据和近乎有序的数据效果也比较好。其实对于上述排序实现还有有优化地方,当子序列比较小直接用插入排序实现,当arr[mid] < arr[j]表明两部分子序列合起来已经有序无需去比较合并,以下是优化后的归并排序实现过程
//归并排序改进类, 提升对近乎有序的数组排序的性能, 稍微降低对无序数组的性能
public class MergeSortImprove {
public void mergeSort(int[] arr, int n) {
mergeSort(arr, 0, n - 1);
}
private void mergeSort(int[] arr, int l, int r) {
//对于小数组
if (r - l <= 15) {
new InsertSort().insertSort(arr, l, r);
return;
}
int mid = (l + r) / 2;
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
// 对于arr[mid] <= arr[mid+1]的情况,不进行merge,因为这时两个区间合起来已经有序,无需归并
if(arr[mid] > arr[mid + 1])
merge(arr, l, mid, r);
}
private void merge(int[] arr, int l, int mid, int r) {
int[] aux = new int[r - l + 1];
for (int i = l; i <= r; i++)
aux[i - l] = arr[i];
int i = l, j = mid + 1;
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = aux[j - l];
j++;
} else if (j > r) {
arr[k] = aux[i - l];
i++;
} else if (aux[i - l] < aux[j - l]) {
arr[k] = aux[i - l];
i++;
} else {
arr[k] = aux[j - l];
j++;
}
}
}
}
以下是优化前归并排序和优化后归并排序的测试代码,测试数据量是1000000,有随机数组、大量重复元素数组、近乎有序的数组。
import cn.zjut.util.SortTestUtil;
public class Main {
public static void main(String[] args){
int n = 1000000;
System.out.println("Test for Random Array, size = " + n + ", random range [0, " + n + ']');
int[] arr1 = SortTestUtil.generateRandomArray(n, 0, n);
int[] arr2 = SortTestUtil.copyIntArray(arr1, n);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
System.out.println("--------------------------------");
System.out.println("Test for Random Array, size = " + n + ", random range [0, " + 10 + ']');
arr1 = SortTestUtil.generateRandomArray(n, 0, 10);
arr2 = SortTestUtil.copyIntArray(arr1, n);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
System.out.println("--------------------------------");
int swapTime = 10;
System.out.println("Test for Random Nearly Ordered Array, size = " + n + ", swap time = " + swapTime);
arr1 = SortTestUtil.generateNearlyOrderedArray(n, swapTime);
arr2 = SortTestUtil.copyIntArray(arr1, n);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
}
}
以下是两者的测试结果
从结果可知,优化效果对于近乎有序的数组比较明前,其他类型数据效果相差不大。归并排序还有另一种实现的过程自底向上实现合并过程,直接对一个个小序列实现合并,子序列逐步增大,最终将原序列实现排序,以下是实现代码
//自底向上归并排序类
public class MergeSortBottomUp {
//归并排序函数
public void mergeSort(int[] arr, int n) {
InsertSort is = new InsertSort();
//优化1
for (int i = 0; i < n; i += 16)
is.insertSort(arr, i, Math.min(i + 15, n - 1));
for (int sz = 16; sz <= n; sz += sz)
for (int i = 0; i < n - sz; i += sz + sz)
//优化2
if (arr[i + sz - 1] > arr[i + sz])
merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, n - 1));
}
private void merge(int[] arr, int l, int mid, int r) {
int[] aux = new int[r - l + 1];
for (int i = l; i <= r; i++)
aux[i - l] = arr[i];
int i = l, j = mid + 1;
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = aux[j - l];
j++;
} else if (j > r) {
arr[k] = aux[i - l];
i++;
} else if (aux[i - l] < aux[j - l]) {
arr[k] = aux[i - l];
i++;
} else {
arr[k] = aux[j - l];
j++;
}
}
}
}
以下是测试代码
import cn.zjut.util.SortTestUtil;
public class Main {
public static void main(String[] args){
int n = 1000000;
System.out.println("Test for Random Array, size = " + n + ", random range [0, " + n + ']');
int[] arr1 = SortTestUtil.generateRandomArray(n, 0, n);
int[] arr2 = SortTestUtil.copyIntArray(arr1, n);
int[] arr3 = SortTestUtil.copyIntArray(arr1, n);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
SortTestUtil.testSort("MergeSortBottomUp", "cn.zjut.sort.MergeSortBottomUp", "mergeSort", arr3, n);
System.out.println("--------------------------------");
System.out.println("Test for Random Array, size = " + n + ", random range [0, " + 10 + ']');
arr1 = SortTestUtil.generateRandomArray(n, 0, 10);
arr2 = SortTestUtil.copyIntArray(arr1, n);
arr3 = SortTestUtil.copyIntArray(arr1, n);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
SortTestUtil.testSort("MergeSortBottomUp", "cn.zjut.sort.MergeSortBottomUp", "mergeSort", arr3, n);
System.out.println("--------------------------------");
int swapTime = 10;
System.out.println("Test for Random Nearly Ordered Array, size = " + n + ", swap time = " + swapTime);
arr1 = SortTestUtil.generateNearlyOrderedArray(n, swapTime);
arr2 = SortTestUtil.copyIntArray(arr1, n);
arr3 = SortTestUtil.copyIntArray(arr1, n);
SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
SortTestUtil.testSort("MergeSortBottomUp", "cn.zjut.sort.MergeSortBottomUp", "mergeSort", arr3, n);
}
}
以下是测试结果
自底向上的归并排序效果与自顶向下的归并排序效果差不多,在统计意义上自顶向下比自底向上归并排序效果好一些,但是自底向上的归并排序可以用于链表的排序过程,因为其排序过程没有使用索引来获取元素。
以上整个过程就是归并排序的所有实现过程。