归并排序的实现原理和动画可以网上找
归并排序实现
import java.util.*;
public class MergeSort {
// 我们的算法类不允许产生任何实例
private MergeSort() {
}
// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
private static void merge(Comparable[] arr, int l, int mid, int r) {
Comparable[] aux = Arrays.copyOfRange(arr, l, r + 1);
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
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].compareTo(aux[j - l]) < 0) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i - l];
i++;
} else { // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j - l];
j++;
}
}
}
// 递归使用归并排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r) {
if (l >= r)
return;
int mid = (l + r) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
public static void sort(Comparable[] arr) {
int n = arr.length;
sort(arr, 0, n - 1);
}
// 测试MergeSort
public static void main(String[] args) {
// Merge Sort是我们学习的第一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
// 注意:不要轻易尝试使用SelectionSort, InsertionSort或者BubbleSort处理100万级的数据
// 否则,你就见识了O(n^2)的算法和O(nlogn)算法的本质差异:)
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("C03_Sorting_Advance.S02_Merge_Sort.MergeSort", arr);
return;
}
}
归并排序和插入排序的性能比较
import java.util.Arrays;
public class Main {
// 比较InsertionSort和MergeSort两种排序算法的性能效率
// 整体而言, MergeSort的性能最优, 对于近乎有序的数组的特殊情况, 见测试2的详细注释
public static void main(String[] args) {
int N = 50000;
// 测试1 一般测试
System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");
Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("C03_Sorting_Advance.S02_Merge_Sort.InsertionSort", arr1);
SortTestHelper.testSort("C03_Sorting_Advance.S02_Merge_Sort.MergeSort", arr2);
System.out.println();
// 测试2 测试近乎有序的数组
// 对于近乎有序的数组, 数组越有序, InsertionSort的时间性能越趋近于O(n)
// 所以可以尝试, 当swapTimes比较大时, MergeSort更快
// 但是当swapTimes小到一定程度, InsertionSort变得比MergeSort快
int swapTimes = 10;
assert swapTimes >= 0;
System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);
arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
arr2 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("C03_Sorting_Advance.S02_Merge_Sort.InsertionSort", arr1);
SortTestHelper.testSort("C03_Sorting_Advance.S02_Merge_Sort.MergeSort", arr2);
return;
}
}
对随机数组的排序,归并排序远远快于插入排序;当然对有序数组的排序,插入排序快于归并排序,因为对有序数组的排序,插入排序的时间复杂度转变为O(n)。
归并排序改进
优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
优化2: 对于小规模数组, 使用插入排序。因为当元素少到一定程度的时候,插入排序的性能高于插入排序
贴上插入排序的代码
// 对arr[l...r]的区间使用InsertionSort排序
public static void sort(Comparable[] arr, int l, int r){
for( int i = l + 1 ; i <= r ; i ++ ){
Comparable e = arr[i];
int j = i;
for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
arr[j] = arr[j-1];
arr[j] = e;
}
}
再次性能比较
public class Main {
// 比较InsertionSort和MergeSort两种排序算法的性能效率
// 整体而言, MergeSort的性能最优, 对于近乎有序的数组的特殊情况, 见测试2的详细注释
public static void main(String[] args) {
int N = 50000;
// 测试1 一般测试
System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");
Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("C03_Sorting_Advance.S03_Merge_Sort_Advance.InsertionSort", arr1);
SortTestHelper.testSort("C03_Sorting_Advance.S03_Merge_Sort_Advance.MergeSort", arr2);
SortTestHelper.testSort("C03_Sorting_Advance.S03_Merge_Sort_Advance.MergeSort2", arr3);
System.out.println();
// 测试2 测试近乎有序的数组
int swapTimes = 10;
assert swapTimes >= 0;
System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);
arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
arr2 = Arrays.copyOf(arr1, arr1.length);
arr3 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("C03_Sorting_Advance.S03_Merge_Sort_Advance.InsertionSort", arr1);
SortTestHelper.testSort("C03_Sorting_Advance.S03_Merge_Sort_Advance.MergeSort", arr2);
SortTestHelper.testSort("C03_Sorting_Advance.S03_Merge_Sort_Advance.MergeSort2", arr3);
return;
}
}
从结果可以看出归并排序优化后的性能好于优化前
自底向上的归并排序
import java.util.*;
public class MergeSortBU {
// 我们的算法类不允许产生任何实例
private MergeSortBU() {
}
// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
private static void merge(Comparable[] arr, int l, int mid, int r) {
Comparable[] aux = Arrays.copyOfRange(arr, l, r + 1);
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
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].compareTo(aux[j - l]) < 0) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i - l];
i++;
} else { // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j - l];
j++;
}
}
}
//没有使用数组一个重要特性(通过索引获取元素),所以可以自底向上的归并排序可以很好的使用O(NlogN)的时间复杂度对链表进行排序。
public static void sort(Comparable[] arr) {
int n = arr.length;
// Merge Sort Bottom Up 无优化版本
// for (int sz = 1; sz < n; sz *= 2)
// for (int i = 0; i < n - sz; i += sz+sz)
// // 对 arr[i...i+sz-1] 和 arr[i+sz...i+2*sz-1] 进行归并
// merge(arr, i, i+sz-1, Math.min(i+sz+sz-1,n-1));
// Merge Sort Bottom Up 优化
// 对于小数组, 使用插入排序优化
for (int i = 0; i < n; i += 16)
InsertionSort.sort(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)
// 对于arr[mid] <= arr[mid+1]的情况,不进行merge
if (arr[i + sz - 1].compareTo(arr[i + sz]) > 0)
merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, n - 1));
}
// 测试 MergeSort BU
public static void main(String[] args) {
// Merge Sort BU 也是一个O(nlogn)复杂度的算法,虽然只使用两重for循环
// 所以,Merge Sort BU也可以在1秒之内轻松处理100万数量级的数据
// 注意:不要轻易根据循环层数来判断算法的复杂度,Merge Sort BU就是一个反例
// 关于这部分陷阱,推荐看我的《玩转算法面试》课程,第二章:《面试中的复杂度分析》:)
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("C03_Sorting_Advance.S04_Merge_Sort_Bottom_Up.MergeSortBU", arr);
return;
}
}
两种归并排序的性能比较
public class Main {
// 比较Merge Sort和Merge Sort Bottom Up两种排序算法的性能效率
// 整体而言, 两种算法的效率是差不多的。但是如果进行仔细测试, 自底向上的归并排序会略胜一筹。
// 更详细的测试, 可以参考课程的这个问题: http://coding.imooc.com/learn/questiondetail/3208.html
// 本章节的代码仓也会给出更详细的测试代码
public static void main(String[] args) {
int N = 1000000;
// 测试1 一般测试
System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");
Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("C03_Sorting_Advance.S04_Merge_Sort_Bottom_Up.MergeSort", arr1);
SortTestHelper.testSort("C03_Sorting_Advance.S04_Merge_Sort_Bottom_Up.MergeSortBU", arr2);
System.out.println();
// 测试2 测试近乎有序的数组
int swapTimes = 10;
assert swapTimes >= 0;
System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);
arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
arr2 = Arrays.copyOf(arr1, arr1.length);
SortTestHelper.testSort("C03_Sorting_Advance.S04_Merge_Sort_Bottom_Up.MergeSort", arr1);
SortTestHelper.testSort("C03_Sorting_Advance.S04_Merge_Sort_Bottom_Up.MergeSortBU", arr2);
return;
}
}
性能上并没有很大的区别。
自底向上的归并排序:没有使用数组一个重要特性(通过索引获取元素),所以自底向上的归并排序可以很好的使用O(NlogN)的时间复杂度对链表进行排序。