源碼分析:
在Collections.sort
中:
|
public static <T extends Comparable<? super T>> void sort(List<T> list) { |
可以發現,最終還是使用了Arrays.sort(a);
的,不同的是:一個針對數組,一個針對集合
擴展:不同版本的內部實現問題
在JDK1.6
以下的時候:調用sort
方法時,默認是使用mergeSort
的算法
而JDK1.7
后,使用TimSort
的算法。源碼如下:JDK7
的sort方法:
|
public static void sort(Object[] a) { |
JDK6
以下的sort方法:
|
public static void sort(Object[] a) { |
當然可以使用下列的方式,在JDK7
依舊使用mergeSort
算法:
|
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); |
但是,從注釋中可以發現,以后將讓TimSort
代替mergeSort
。
根據這篇JDK7中的排序算法詳解—Collections.sort和Arrays.sort找到了解決方法:
而在Java 7中,內部實現換成了TimSort,其對對象間比較的實現要求更加嚴格:
Comparator
的實現必須保證以下幾點(出自這兒)):
- sgn(compare(x, y)) == -sgn(compare(y, x))
- (compare(x, y)>0) && (compare(y, z)>0) 意味着 compare(x, z)>0
- compare(x, y)==0 意味着對於任意的z:sgn(compare(x, z))==sgn(compare(y, z)) 均成立
所以,compare
要相應的改成:
|
public int compare(ComparatorTest o1, ComparatorTest o2) { |
先對相等的情況判斷,再對大小的判斷。
算法分析
既然這個算法比之前快排要快,那么肯定有它的巧妙之處,我們來仔細看看吧。
算法步驟
1.對於很小的數組(長度小於27),會使用插入排序。
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.選擇兩個點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.遞歸的將第一二三部分排序。
圖表演示

注:圖片來自Vladimir Yaroslavskiy的論文。
算法源碼
- //對外公開的兩個sort方法
- public static void sort(int[] a) {
- sort(a, 0, a.length);
- }
- public static void sort(int[] a, int fromIndex, int toIndex) {
- rangeCheck(a.length, fromIndex, toIndex);
- dualPivotQuicksort(a, fromIndex, toIndex - 1, 3);
- }
- //對數組的邊界檢測
- private static void rangeCheck(int length, int fromIndex, int toIndex) {
- if (fromIndex > toIndex) {
- throw new IllegalArgumentException("fromIndex > toIndex");
- }
- if (fromIndex < 0) {
- throw new ArrayIndexOutOfBoundsException(fromIndex);
- }
- if (toIndex > length) {
- throw new ArrayIndexOutOfBoundsException(toIndex);
- }
- }
- //交換數組中兩個元素
- private static void swap(int[] a, int i, int j) {
- int temp = a[i];
- a[i] = a[j];
- a[j] = temp;
- }
- /**
- * 雙軸快排的具體實現
- * @param a 待排序數組
- * @param left 數組需排序上界
- * @param right 數組需排序下界
- * @param div 理解為從何位置取軸
- */
- private static void dualPivotQuicksort(int[] a, int left,int right, int div) {
- int len = right - left;
- //數組長度如果很小(27),則直接用插入排序對其排序
- if (len < 27) {
- for (int i = left + 1; i <= right; i++) {
- for (int j = i; j > left && a[j] < a[j - 1]; j--) {
- swap(a, j, j - 1);
- }
- }
- return;
- }
- //取到位於1/div和div-1/div位置的點,並用他們來做軸
- int third = len / div;
- int m1 = left + third;
- int m2 = right - third;
- if (m1 <= left) {
- m1 = left + 1;
- }
- if (m2 >= right) {
- m2 = right - 1;
- }
- //確保left是小的,right是大的
- if (a[m1] < a[m2]) {
- swap(a, m1, left);
- swap(a, m2, right);
- }
- else {
- swap(a, m1, right);
- swap(a, m2, left);
- }
- // 兩個軸
- int pivot1 = a[left];
- int pivot2 = a[right];
- // 代表比p1小和比p2大的兩個指針
- int less = left + 1;
- int great = right - 1;
- // 開始取出less到great之間的未知大小數據,與兩個軸比較
- // 並且將數據放入正確的區域后調整各個指針
- for (int k = less; k <= great; k++) {
- //如果取出的數比p1小,那么直接到less左側,並且less右移
- if (a[k] < pivot1) {
- swap(a, k, less++);
- }
- //如果取出的數比p2大,那么首先確定great左側沒有比p2大的數
- //然后與great位置的數字交換,great左移
- //此時,great交換的數字肯定是比p2小或者相等的(首先確定過)
- //那么此時再與p1相比,處理這個數的區間
- else if (a[k] > pivot2) {
- while (k < great && a[great] > pivot2) {
- great--;
- }
- swap(a, k, great--);
- if (a[k] < pivot1) {
- swap(a, k, less++);
- }
- }
- //如果這個數比p1大但是比p2小,則不需要交換,只需將k指針右移
- }
- //將p1與less左側的第一個數交換
- swap(a, less - 1, left);
- //將p2與great右側的第一個數交換
- swap(a, great + 1, right);
- // 計算出在兩軸大小之間的個數
- int dist = great - less;
- //如果這個數很小(13),那么取軸的點向兩邊偏
- if (dist < 13) {
- div++;
- }
- // 對三個子區間分別排序,因為less-1和great+1是軸,已經排好了序
- // 所以不需要比較
- dualPivotQuicksort(a, left, less - 2, div);
- dualPivotQuicksort(a, great + 2, right, div);
- // 如果在中間區間的數字很多,那么排除掉一些相等的元素再進行排序
- if (dist > len - 13 && pivot1 != pivot2) {
- for (int k = less; k <= great; k++) {
- if (a[k] == pivot1) {
- swap(a, k, less++);
- }
- else if (a[k] == pivot2) {
- swap(a, k, great--);
- if (a[k] == pivot1) {
- swap(a, k, less++);
- }
- }
- }
- }
- // 對中間的區間排序
- if (pivot1 < pivot2) {
- dualPivotQuicksort(a, less, great, div);
- }
- }
總結
雙軸排序利用了區間相鄰的特性,對原本的快排進行了效率上的提高,很大程度上是利用了數學的一些特性,果然,算法跟數學還是息息相關的吖。