Java 排序源码分析
- Collections.sort()
- Arrays.sort()
- 基本数据类型
- 为什么要三种排序方法混用
- 非基本数据类型
- 为什么对非基本数据类型不用快排而用归并
- 参考
最近刷算法题经常用到Arrays.sort(), Collections.sort()等方法。相信写java的大佬们对排序算法有很好的实现,所以特地点开源码学习学习。
本文代码分析基于java1.8
Collections.sort()
点开Collections.sort() -> list.sort(null):
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
可以发现,对List类的排序是通过调用Arrays.sort()实现的。
Arrays.sort()
点开Arrays类的结构,可以看到有大量重载的sort方法,其大致可基于入参分为两类:除了boolean的基本数据类型,非基本数据类型(泛型T)。
下面对二者分别进行分析
基本数据类型
以int为例:
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
根据该方法注释:
This algorithm offers O(n log(n)) performance on many data sets that cause other
quicksorts to degrade to quadratic performance, and is typically faster than traditional (one-pivot) Quicksort implementations.
DualPivotQuicksort为某种比普通快排更优秀的快排,其具体实现重点代码有:
static void sort(int[] a, int left, int right,
int[] work, int workBase, int workLen) {
// Use Quicksort on small arrays
if (right - left < QUICKSORT_THRESHOLD) {
sort(a, left, right, true);
return;
}
//...
// Check if the array is nearly sorted
//...
/*
* The array is not highly structured,
* use Quicksort instead of merge sort.
*/
if (++count == MAX_RUN_COUNT) {
sort(a, left, right, true);
return;
}
}
// Merging
//...
}
}
结合常量注释分析可得:
-
QUICKSORT_THRESHOLD = 286
为是否使用快排的阈值。
当数组长度小于该阈值时,使用"快排",否则进入下一步判断 -
MAX_RUN_LENGTH = 67
为判断数组是否已有一定有序性的阈值。
当有序性大于该阈值时,使用"快排",否则进入归并排序
点开"快排"方法sort(a, left, right, true)
可发现,事情还没那么简单:
private static void sort(int[] a, int left, int right, boolean leftmost) {
// Use insertion sort on tiny arrays
if (length < INSERTION_SORT_THRESHOLD) {
//insertion sort
}
//quick sort
}
}
没错!还有一层:INSERTION_SORT_THRESHOLD = 47
为是否使用插入排序的阈值
当数组长度小于该阈值时,使用插入排序,否则进行快排
总结
为什么要三种排序方法混用
- 对于小数组来说,插入排序性能更高,时间复杂度相差不大
- 对于大数组,若数组无序组够多,使用快速排序(快排数据越无序越快)
- 若数组太大且有一定有序性,使用稳定的归并排序
非基本数据类型
对于非基本数据类型,如非特别要求,使用TimSort算法
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
Timsort是一种结合了归并排序和插入排序的混合排序算法。可视为一种归并排序,此处不做展开。
为什么对非基本数据类型不用快排而用归并
重点在于 快排不是稳定算法,而归并排序是!
- 对于基本数组类型,稳定性没有太大意义,所以可以使用不稳定的快排,但是对于对象类型,稳定性是比较重要的,对象相等的复杂性让我们没办法保证每个人都会重写正确的equal方法,故使用稳定算法的归并排序和插入排序结合的TimSort算法。
- 对于归并排序来说,比较次数比快速排序少,移动次数比快排多,而对于对象来说,比较是相对耗时的操作,所以并不适合快排,对于基本数据类型来说,比较和移动都不怎么耗时,所以用归并和快速都可以。
参考
- Java排序算法Sort源码分析