在Java中总是能很方便的调用别人已经写好的方法,无论是导入的jar包还是JDK自带的工具包。就比如JDK中对数组的排序功能,只要是个数组传进去就能排序,这无疑是大大提高了开发的效率,流行的排序有很多,那它里面到底是用了哪一种排序?接下来对照源码分析一波~
sort支持各种各样类型数据数组的排序~
我们就来看个int[ ]排序的源码叭,一进去是这样的~
点进去~
我也看不懂~但是度娘是这么说的
嗯,接着看类结构~
final class DualPivotQuicksort {
private DualPivotQuicksort() {}
//归并排序中的最大运行次数67
private static final int MAX_RUN_COUNT = 67;
//归并排序中运行的最大长度33
private static final int MAX_RUN_LENGTH = 33;
//长度小于286的数组,优先采用快排而不是归并
private static final int QUICKSORT_THRESHOLD = 286;
//长度小于47的数组,优先采用插入而不是快排
private static final int INSERTION_SORT_THRESHOLD = 47;
大概是这么个意思哈~ 插入| 47| 快排| 286| 归并
//长度大于29,用计数排序而不是插入(针对byte数组)
private static final int COUNTING_SORT_THRESHOLD_FOR_BYTE = 29;
//字符数组长度大于3200,计数排序优先于快速排序(针对char数组)
private static final int COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR = 3200;
//接下来是很多方法……
}
emmm,要看源码了……
static void sort(int[] a, int left, int right,
int[] work, int workBase, int workLen) {
// Use Quicksort on small arrays
// 小于286就用快排
if (right - left < QUICKSORT_THRESHOLD) {
//这里不是递归!!!调用了快排
sort(a, left, right, true);
return;
}
1. 首先进入第一个分支,这里是跳转过去的快排,更小的数组使用插入
private static void sort(int[] a, int left, int right, boolean leftmost) {
int length = right - left + 1;
// Use insertion sort on tiny arrays
//更小的话使用插入
if (length < INSERTION_SORT_THRESHOLD) {
if (leftmost) {
for (int i = left, j = i; i < right; j = ++i) {
int ai = a[i + 1];
while (ai < a[j]) {
a[j + 1] = a[j];
if (j-- == left) {
break;
}
}
a[j + 1] = ai;
}
} else {
/*
* Skip the longest ascending sequence.
*/
do {
if (left >= right) {
return;
}
} while (a[++left] >= a[left - 1]);
for (int k = left; ++left <= right; k = ++left) {
int a1 = a[k], a2 = a[left];
if (a1 < a2) {
a2 = a1; a1 = a[left];
}
while (a1 < a[--k]) {
a[k + 2] = a[k];
}
a[++k + 1] = a1;
while (a2 < a[--k]) {
a[k + 1] = a[k];
}
a[k + 1] = a2;
}
int last = a[right];
while (last < a[--right]) {
a[right + 1] = a[right];
}
a[right + 1] = last;
}
return;
}
否则使用双轴快排,基本的思想是这样的,源码就不看了(太复杂~~)
* Partitioning:
*
* left part center part right part
* +--------------------------------------------------------------+
* | < pivot1 | pivot1 <= && <= pivot2 | ? | > pivot2 |
* +--------------------------------------------------------------+
* ^ ^ ^
* | | |
* less k great
*
* Invariants:
*
* all in (left, less) < pivot1
* pivot1 <= all in [less, k) <= pivot2
* all in (great, right) > pivot2
*
* Pointer k is the first index of ?-part.
*/
算法步骤(参照博客)
1.对于很小的数组(长度小于47),会使用插入排序。
2.选择两个点P1,P2作为轴心,比如我们可以使用第一个元素和最后一个元素。
3.P1必须比P2要小,否则将这两个元素交换,现在将整个数组分为四部分:
(1)第一部分:比P1小的元素。
(2)第二部分:比P1大但是比P2小的元素。
(3)第三部分:比P2大的元素。
(4)第四部分:尚未比较的部分。
在开始比较前,除了轴点,其余元素几乎都在第四部分,直到比较完之后第四部分没有元素。
4.从第四部分选出一个元素a[K],与两个轴心比较,然后放到第一二三部分中的一个。
5.移动L,K,G指向。
6.重复 4 5 步,直到第四部分没有元素。
7.将P1与第一部分的最后一个元素交换。将P2与第三部分的第一个元素交换。
8.递归的将第一二三部分排序。
2. 这是不进入第一个分支的源码,先检查数组是否已经近乎有序
//-------------------主要检查这个数组是不是近乎有序-------------------
for (int k = left; k < right; run[count] = k) {
//有序就循环往下判断
if (a[k] < a[k + 1]) {
while (++k <= right && a[k - 1] <= a[k]);
//无序则交换
} else if (a[k] > a[k + 1]) {
while (++k <= right && a[k - 1] >= a[k]);
for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {
int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
}
//前后元素相等
} else {
//最多循环MAX_RUN_LENGTH次,如果全部相等(近乎有序)用插入排序
for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
if (--m == 0) {
sort(a, left, right, true);
return;
}
}
}
开始归并
//---------------------开始归并--------------------------
// Determine alternation base for merge
byte odd = 0;
for (int n = 1; (n <<= 1) < count; odd ^= 1);
// Use or create temporary array b for merging
int[] b; // temp array; alternates with a
int ao, bo; // array offsets from 'left'
int blen = right - left; // space needed for b
if (work == null || workLen < blen || workBase + blen > work.length) {
work = new int[blen];
workBase = 0;
}
if (odd == 0) {
System.arraycopy(a, left, work, workBase, blen);
b = a;
bo = 0;
a = work;
ao = workBase - left;
} else {
b = work;
ao = 0;
bo = workBase - left;
}
// Merging
for (int last; count > 1; count = last) {
for (int k = (last = 0) + 2; k <= count; k += 2) {
int hi = run[k], mi = run[k - 1];
for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) {
if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) {
b[i + bo] = a[p++ + ao];
} else {
b[i + bo] = a[q++ + ao];
}
}
run[++last] = hi;
}
if ((count & 1) != 0) {
for (int i = right, lo = run[count - 1]; --i >= lo;
b[i + bo] = a[i + ao]
);
run[++last] = right;
}
int[] t = a; a = b; b = t;
int o = ao; ao = bo; bo = o;
}
}
所以到这里也大体知道一个大的流程框架了。
排序里面实质调用了 DualPivotQuicksort()方法,而 DualPivotQuicksort类中也写了很多的阈值为了区分数组的大小进而选择合适的排序方法。
一进去先判断数组大小和QUICKSORT_THRESHOLD(86)的大小,如果比它小就进入快排。
快排一进去先比较数组大小和INSERTION_SORT_THRESHOLD(47)的大小,如果比它小就直接插入排序。
而快速排序是双轴的,把数组用两个pivot(随机选取)分成三段(还有一段待排区间)效率更高。
对近乎有序的数组进行识别处理,提高效率。