3-3 归并排序法的优化
本节介绍上一节实现的归并排序的两个优化:
1、在 mergeOfTwoSortArray
对两个有序的数组进行归并的时候,如果两个数组合并之前就是有序的数组,就不用再复制来复制去的了;
2、在归并的子过程中,如果待排序的数组元素个数很少的情况下,可以使用插入排序,因为插入排序对于近乎有序的数组而言,可以提前终止循环,从而提高整体排序的效率。
下面我们具体来说明:
1、归并排序写好以后,我们可以尝试将其与插入排序进行一个 5000 级别的数据排序的比较测试。
结论是显而易见的,因为插入排序的时间复杂度是 O(n^2),归并排序的时间复杂度是 O(nlogn)。所以,在绝大多数情况下,归并排序的性能较好,插入排序的效率较低。
2、但是我们还要认识到一点,在待排序的数组在近乎有序的前提下,插入排序可以达到 O(n) 的时间复杂度,效果非常好;
3、归并(merge)排序的优化思路:
(1)事实上,当 arr[mid]<=arr[mid+1] 的时候是不用 merge 的,如图所示:
代码实现:
if (arr[mid] < arr[mid + 1]) {
return;
}
(2)递归到底的时候,可以使用插入排序优化,例如在处理 16 个元素的数组时候,不用继续递归,此时使用插入排序提升性能。
“递归到底”这件事情可以由我们自己定义:
原来我们定义的递归到底这件事情是:当数组区间里只有一个元素的时候,这个元素就是有序的,所以递归不能再进行下去了。
现在我们定义的递归到底这件事情是:当数组区间里的元素是有限个的时候,我们改用插入排序法来完成排序。
代码实现:
if(right-left<=15){
// 使用插入排序去排序这部分的数组元素
}
代码实现:
/**
* 对数组给定的部分使用插入排序
*
* @param arr 给定数组
* @param left 左边界,能取到
* @param right 右边界,能取到
*/
private void insertSort(int[] arr, int left, int right) {
for (int i = left + 1; i <= right; i++) { // 第 1 遍不用插入,所以是总长度减去 1
int temp = arr[i];
int j;
for (j = i - 1; j > left; j--) {
if (arr[j] > temp) { // 后移一位
arr[j + 1] = arr[j];
} else {
break;
}
}
arr[j] = temp;
}
}
使用 15 是不是效果最优的呢?这是一个经验值。
下面编写测试用例:
/**
* 测试归并排序及其性能优化
*/
@Test
public void test07() {
int[] randomArray1 = SortTestHelper.generateRandomArray(1000000, 1, 500000000);
int[] randomArray2 = SortTestHelper.copyFromOldArray(randomArray1);
SortTestHelper.testSortEfficiency(new MergeSort(), randomArray1);
SortTestHelper.testSortEfficiency(new MergeSortOptimize1(), randomArray2);
SortTestHelper.testSorted(randomArray1);
SortTestHelper.testSorted(randomArray2);
}
执行结果:
您所使用的排序算法是 => merge sort(归并排序)
排序算法耗时 => 0.181 秒
您所使用的排序算法是 => 归并排序性能优化1
排序算法耗时 => 0.101 秒
给定数组按照升序排序!
给定数组按照升序排序!