jdk1.6與jdk1.7list集合排序區別與算法


源碼分析:

Collections.sort中:

 
 public static <T extends Comparable<? super T>> void sort(List<T> list) {
        Object[] a = list.toArray();
        Arrays.sort(a);
        ListIterator<T> i = list.listIterator();
        for (int j=0; j<a.length; j++) {
            i.next();
            i.set((T)a[j]);
        }
    }

 

可以發現,最終還是使用了Arrays.sort(a);的,不同的是:一個針對數組,一個針對集合

擴展:不同版本的內部實現問題

JDK1.6以下的時候:調用sort方法時,默認是使用mergeSort的算法
JDK1.7后,使用TimSort的算法。源碼如下:
JDK7的sort方法:

 
public static void sort(Object[] a) {  
        if (LegacyMergeSort.userRequested)  
            legacyMergeSort(a);  
        else  
            ComparableTimSort.sort(a);  
    }  
    /** To be removed in a future release. */  
    private static void legacyMergeSort(Object[] a) {  
        Object[] aux = a.clone();  
        mergeSort(aux, a, 0, a.length, 0);  
    }

 

JDK6以下的sort方法:

 
public static void sort(Object[] a) {  
        Object[] aux = (Object[])a.clone();  
        mergeSort(aux, a, 0, a.length, 0);  
    }

 

當然可以使用下列的方式,在JDK7依舊使用mergeSort算法:

 
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

 

但是,從注釋中可以發現,以后將讓TimSort代替mergeSort

根據這篇JDK7中的排序算法詳解—Collections.sort和Arrays.sort找到了解決方法:

而在Java 7中,內部實現換成了TimSort,其對對象間比較的實現要求更加嚴格:
Comparator的實現必須保證以下幾點(出自這兒)):

  1. sgn(compare(x, y)) == -sgn(compare(y, x))
  2. (compare(x, y)>0) && (compare(y, z)>0) 意味着 compare(x, z)>0
  3. compare(x, y)==0 意味着對於任意的z:sgn(compare(x, z))==sgn(compare(y, z)) 均成立

所以,compare要相應的改成:

 
public int compare(ComparatorTest o1, ComparatorTest o2) {
    return o1.getValue() == o2.getValue() ? 0 : 
                (o1.getValue() > o2.getValue() ? 1 : -1);
}

 

先對相等的情況判斷,再對大小的判斷。

 

算法分析

既然這個算法比之前快排要快,那么肯定有它的巧妙之處,我們來仔細看看吧。

算法步驟

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.遞歸的將第一二三部分排序。

圖表演示

注:圖片來自Vladimir Yaroslavskiy的論文。

算法源碼 

  1. //對外公開的兩個sort方法  
  2. public static void sort(int[] a) {  
  3.     sort(a, 0, a.length);  
  4. }  
  5. public static void sort(int[] a, int fromIndex, int toIndex) {  
  6.     rangeCheck(a.length, fromIndex, toIndex);  
  7.     dualPivotQuicksort(a, fromIndex, toIndex - 1, 3);  
  8. }  
  9. //對數組的邊界檢測  
  10. private static void rangeCheck(int length, int fromIndex, int toIndex) {  
  11.     if (fromIndex > toIndex) {  
  12.         throw new IllegalArgumentException("fromIndex > toIndex");  
  13.     }  
  14.     if (fromIndex < 0) {  
  15.         throw new ArrayIndexOutOfBoundsException(fromIndex);  
  16.     }  
  17.     if (toIndex > length) {  
  18.         throw new ArrayIndexOutOfBoundsException(toIndex);  
  19.     }  
  20. }  
  21. //交換數組中兩個元素  
  22. private static void swap(int[] a, int i, int j) {  
  23.     int temp = a[i];  
  24.     a[i] = a[j];  
  25.     a[j] = temp;  
  26. }  
  27. /** 
  28.  * 雙軸快排的具體實現 
  29.  * @param a     待排序數組 
  30.  * @param left  數組需排序上界 
  31.  * @param right 數組需排序下界 
  32.  * @param div   理解為從何位置取軸 
  33.  */  
  34. private static void dualPivotQuicksort(int[] a, int left,int right, int div) {  
  35.     int len = right - left;  
  36.     //數組長度如果很小(27),則直接用插入排序對其排序  
  37.     if (len < 27) {  
  38.         for (int i = left + 1; i <= right; i++) {  
  39.             for (int j = i; j > left && a[j] < a[j - 1]; j--) {  
  40.                 swap(a, j, j - 1);  
  41.             }  
  42.         }  
  43.         return;  
  44.     }  
  45.     //取到位於1/div和div-1/div位置的點,並用他們來做軸  
  46.     int third = len / div;  
  47.     int m1 = left + third;  
  48.     int m2 = right - third;  
  49.     if (m1 <= left) {  
  50.         m1 = left + 1;  
  51.     }  
  52.     if (m2 >= right) {  
  53.         m2 = right - 1;  
  54.     }  
  55.     //確保left是小的,right是大的  
  56.     if (a[m1] < a[m2]) {  
  57.         swap(a, m1, left);  
  58.         swap(a, m2, right);  
  59.     }  
  60.     else {  
  61.         swap(a, m1, right);  
  62.         swap(a, m2, left);  
  63.     }  
  64.     // 兩個軸  
  65.     int pivot1 = a[left];  
  66.     int pivot2 = a[right];  
  67.     // 代表比p1小和比p2大的兩個指針  
  68.     int less = left + 1;  
  69.     int great = right - 1;  
  70.     // 開始取出less到great之間的未知大小數據,與兩個軸比較  
  71.     // 並且將數據放入正確的區域后調整各個指針  
  72.     for (int k = less; k <= great; k++) {  
  73.         //如果取出的數比p1小,那么直接到less左側,並且less右移  
  74.         if (a[k] < pivot1) {  
  75.             swap(a, k, less++);  
  76.         }   
  77.         //如果取出的數比p2大,那么首先確定great左側沒有比p2大的數  
  78.         //然后與great位置的數字交換,great左移  
  79.         //此時,great交換的數字肯定是比p2小或者相等的(首先確定過)  
  80.         //那么此時再與p1相比,處理這個數的區間  
  81.         else if (a[k] > pivot2) {  
  82.             while (k < great && a[great] > pivot2) {  
  83.                 great--;  
  84.             }  
  85.             swap(a, k, great--);  
  86.             if (a[k] < pivot1) {  
  87.                 swap(a, k, less++);  
  88.             }  
  89.         }  
  90.         //如果這個數比p1大但是比p2小,則不需要交換,只需將k指針右移  
  91.     }  
  92.     //將p1與less左側的第一個數交換  
  93.     swap(a, less - 1, left);  
  94.     //將p2與great右側的第一個數交換  
  95.     swap(a, great + 1, right);  
  96.     // 計算出在兩軸大小之間的個數  
  97.     int dist = great - less;  
  98.     //如果這個數很小(13),那么取軸的點向兩邊偏  
  99.     if (dist < 13) {  
  100.         div++;  
  101.     }  
  102.     // 對三個子區間分別排序,因為less-1和great+1是軸,已經排好了序  
  103.     // 所以不需要比較  
  104.     dualPivotQuicksort(a, left, less - 2, div);  
  105.     dualPivotQuicksort(a, great + 2, right, div);  
  106.     // 如果在中間區間的數字很多,那么排除掉一些相等的元素再進行排序  
  107.     if (dist > len - 13 && pivot1 != pivot2) {  
  108.         for (int k = less; k <= great; k++) {  
  109.             if (a[k] == pivot1) {  
  110.                 swap(a, k, less++);  
  111.             }  
  112.             else if (a[k] == pivot2) {  
  113.                 swap(a, k, great--);  
  114.                 if (a[k] == pivot1) {  
  115.                     swap(a, k, less++);  
  116.                 }  
  117.             }  
  118.         }  
  119.     }  
  120.     // 對中間的區間排序  
  121.     if (pivot1 < pivot2) {  
  122.         dualPivotQuicksort(a, less, great, div);  
  123.     }  
  124. }  


總結

雙軸排序利用了區間相鄰的特性,對原本的快排進行了效率上的提高,很大程度上是利用了數學的一些特性,果然,算法跟數學還是息息相關的吖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM