在學習過程中觀察到Arrays.sort(arr)演算法可以直接進行排序,但不清楚底層的程式碼邏輯是什麼樣子,記得自己之前在面試題裡面也有面試官問這個問題,只能說研究之後發現還是比較複雜的,並不是網上說的快排或者二分插入之類的。
首先看原始碼:
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
它呼叫了DualPivotQuicksort的sort方法,乍一看以為是快排,這是很多網上的博主的說法,繼續點開向下看(程式碼太長,沒耐心看可以直接跳過該段程式碼QWQ):
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;
}
/*
* Index run[i] is the start of i-th run
* (ascending or descending sequence).
*/
int[] run = new int[MAX_RUN_COUNT + 1];
int count = 0; run[0] = left;
// Check if the array is nearly sorted
for (int k = left; k < right; run[count] = k) {
if (a[k] < a[k + 1]) { // ascending
while (++k <= right && a[k - 1] <= a[k]);
} else if (a[k] > a[k + 1]) { // descending
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 { // equal
for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
if (--m == 0) {
sort(a, left, right, true);
return;
}
}
}
/*
* The array is not highly structured,
* use Quicksort instead of merge sort.
*/
if (++count == MAX_RUN_COUNT) {
sort(a, left, right, true);
return;
}
}
// Check special cases
// Implementation note: variable "right" is increased by 1.
if (run[count] == right++) { // The last run contains one element
run[++count] = right;
} else if (count == 1) { // The array is already sorted
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;
}
}
程式碼很長,簡要翻譯過來,這裡分了好幾種情況:
-
陣列長度小於286
這裡又會呼叫一個sort方法,點開該sort(),又會劃分情況:
-
陣列長度小於47,
當leftmost(匯入的一個布林引數)為true,則使用直接插入排序;
否則會呼叫另一種插入辦法,這裡可以觀察到一個註釋:
/* * Every element from adjoining part plays the role * of sentinel, therefore this allows us to avoid the * left range check on each iteration. Moreover, we use * the more optimized algorithm, so called pair insertion * sort, which is faster (in the context of Quicksort) * than traditional implementation of insertion sort. */
大致意思是:相鄰部分的每個元素都扮演著哨兵的角色,因此這允許我們避免在每次迭代中進行左範圍檢查。此外,我們使用了更優化的演算法,即所謂的成對插入排序,它比插入排序的傳統實現更快(在快速排序的上下文中)。
不過注意到,原函式引數傳參在這裡leftmost為true,所以一定是直接插入排序,以上作為了解。
-
陣列長度大於47,採用一種快速排序的辦法,這裡因為程式碼太長,學藝不精,參考了一下白春雨大佬的文章分析:
至於大過INSERTION_SORT_THRESHOLD(47)的,用一種快速排序的方法:
1.從數列中挑出五個元素,稱為 “基準”(pivot);
2.重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分割槽退出之後,該基準就處於數列的中間位置。這個稱為分割槽(partition)操作;
3.遞迴地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
-
-
當陣列長度大於286時
此時回到那段很長很長的程式碼段,在判斷小於286的長度陣列之後,從註解中:
// Check if the array is nearly sorted
這裡是指檢查陣列元素是不是快要排列好了,或者書面一點說,是不是有一定結構了,然後看後面的for迴圈,注意到一段程式碼:
if (++count == MAX_RUN_COUNT) { sort(a, left, right, true); return; }
這裡的sort和我們上面在陣列長度小於286時的那個sort方法是同一個方法,而針對這個count,是用來記錄逆序組的,打個比方:
此時有一個陣列為1 5 6 9 8 7 2 3
當陣列認定我們的順序應該為升序時,從第一個數開始數,此時9 8 7 2為降序,這就是逆序,將這四個陣列合成一個組稱為逆序組,然後再從3開始往後看。
當統計到一個逆序組時,count++,所以可以看出,count是用來記逆序組的,那麼逆序組越多,這個結構就越混亂
MAX_RUN_COUNT == 67 ,因此當count一直加到67時,就說明已經在一個混亂的臨界值了,此時執行sort()方法
通過這一段分析,我們理一下思路:
如果陣列能執行到這裡,說明陣列的長度大於等於286。符合該條件時,我們要判斷這個陣列是否有一定的結構:
(1)count<67,說明不是那麼混亂,有一定結構,跳過;
(2)count>=67,說明已經混亂了,沒有結構,執行sort方法,而已知陣列長度大於等於286,那麼必然大於47,一定執行快速排序。
跳過之後,經過程式碼的一大堆前置操作,最後看見下面的程式碼裡面一行註釋:
//Merging
顯然,這裡後面用到歸併排序了,不詳細贅述。
好了,最後總結:
- 陣列長度小於286時,根據陣列長度,分兩種情況:
- 陣列長度小於47,使用直接插入排序
- 陣列長度大於47,使用快速排序
- 陣列長度大於286時,根據陣列排序情況,分兩種情況:
- 陣列內順序較為混亂,即count逆序組數大於等於67,使用快速排序
- 陣列內有一定順序,即count逆序組數小於67,使用歸併排序
歡迎各位大佬討論
參考資料:
《Java的Arrays.sort()方法到底用的什麼排序演算法》 https://www.cnblogs.com/baichunyu/p/11935995.html 作者:白春雨
- 陣列長度小於286時,根據陣列長度,分兩種情況: