1. 程式人生 > >java.util.ComparableTimSort中的sort()方法簡單分析

java.util.ComparableTimSort中的sort()方法簡單分析

ray urn popu assert 起源 排序算法 gac ont nts

TimSort算法是一種起源於歸並排序和插入排序的混合排序算法,設計初衷是為了在真實世界中的各種數據中能夠有較好的性能。

該算法最初是由Tim Peters於2002年在Python語言中提出的。

TimSort 是一個歸並排序做了大量優化的版本號。

對歸並排序排在已經反向排好序的輸入時表現O(n2)的特點做了特別優化。對已經正向排好序的輸入降低回溯。對兩種情況混合(一會升序。一會降序)的輸入處理比較好。

在jdk1.7之後。Arrays類中的sort方法有一個分支推斷,當LegacyMergeSort.userRequested為true的情況下,採用legacyMergeSort,否則採用ComparableTimSort。而且在legacyMergeSort的凝視上標明了該方法會在以後的jdk版本號中廢棄,因此以後Arrays類中的sort方法將採用ComparableTimSort類中的sort方法。

<span style="font-family:Microsoft YaHei;">public static void sort(Object[] a, int fromIndex, int toIndex) {
    if (LegacyMergeSort.userRequested)
        legacyMergeSort(a, fromIndex, toIndex);
    else
        ComparableTimSort.sort(a, fromIndex, toIndex);
} </span>
以下是ComparableTimSort的sort方法
<span style="font-family:Microsoft YaHei;">static void sort(Object[] a) {
      sort(a, 0, a.length);
}

static void sort(Object[] a, int lo, int hi) {
    rangeCheck(a.length, lo, hi);
    int nRemaining  = hi - lo;
    if (nRemaining < 2)
        return;  // Arrays of size 0 and 1 are always sorted

    // If array is small, do a "mini-TimSort" with no merges
    if (nRemaining < MIN_MERGE) {
        int initRunLen = countRunAndMakeAscending(a, lo, hi);
        binarySort(a, lo, hi, lo + initRunLen);
        return;
    }

    /**
     * March over the array once, left to right, finding natural runs,
     * extending short natural runs to minRun elements, and merging runs
     * to maintain stack invariant.
     */
    ComparableTimSort ts = new ComparableTimSort(a);
    int minRun = minRunLength(nRemaining);
    do {
        // Identify next run
        int runLen = countRunAndMakeAscending(a, lo, hi);

        // If run is short, extend to min(minRun, nRemaining)
        if (runLen < minRun) {
            int force = nRemaining <= minRun ? nRemaining : minRun;
            binarySort(a, lo, lo + force, lo + runLen);
            runLen = force;
        }

        // Push run onto pending-run stack, and maybe merge
        ts.pushRun(lo, runLen);
        ts.mergeCollapse();

        // Advance to find next run
        lo += runLen;
        nRemaining -= runLen;
    } while (nRemaining != 0);

    // Merge all remaining runs to complete sort
    assert lo == hi;
    ts.mergeForceCollapse();
    assert ts.stackSize == 1;
}</span>
(1)傳入的待排序數組若小於閾值MIN_MERGE(Java實現中為32。Python實現中為64)。則調用 binarySort,這是一個不包括合並操作的 mini-TimSort

a) 從數組開始處找到一組連接升序或嚴格降序(找到後翻轉)的數
b) Binary Sort:使用二分查找的方法將興許的數插入之前的已排序數組。binarySort 對數組 a[lo:hi] 進行排序,而且a[lo:start] 是已經排好序的。算法的思路是對a[start:hi] 中的元素。每次使用binarySearch 為它在 a[lo:start] 中找到對應位置,並插入。

(2)開始真正的TimSort過程:

(2.1) 選取minRun大小,之後待排序數組將被分成以minRun大小為區塊的一塊塊子數組

a) 假設數組大小為2的N次冪,則返回16(MIN_MERGE / 2)
b) 其它情況下,逐位向右位移(即除以2),直到找到介於16和32間的一個數

  • minRun
<span style="font-family:Microsoft YaHei;">private static int minRunLength(int n) {
        assert n >= 0;
        int r = 0;      // Becomes 1 if any 1 bits are shifted off
        while (n >= MIN_MERGE) {
            r |= (n & 1);
            n >>= 1;
        }
        return n + r;
    }</span>
這個函數依據 n 計算出相應的 natural run 的最小長度。

MIN_MERGE 默覺得32,假設n小於此值,那麽返回n 本身。否則會將 n 不斷地右移。直到少於 MIN_MERGE,同一時候記錄一個 r 值,r 代表最後一次移位n時。n最低位是0還是1。 最後返回 n + r,這也意味著僅僅保留最高的 5 位。再加上第六位。

(2.2)do-while

(2.2.1)找到初始的一組升序數列countRunAndMakeAscending 會找到一個run 。這個run 必須是已經排序的。而且函數會保證它為升序,也就是說,假設找到的是一個降序的。會對其進行翻轉。

(2.2.2)若這組區塊大小小於minRun,則將興許的數補足,利用binarySortrun 進行擴展。而且擴展後,run 仍然是有序的。

(2.2.3)當前的 run 位於 a[lo:runLen] ,將其入棧ts.pushRun(lo, runLen);//為興許merge各區塊作準備:記錄當前已排序的各區塊的大小

(2.2.4)對當前的各區塊進行merge,merge會滿足下面原則(如果X,Y,Z為相鄰的三個區塊):

a) 僅僅對相鄰的區塊merge
b) 若當前區塊數僅為2,If X<=Y。將X和Y merge
b) 若當前區塊數>=3,If X<=Y+Z。將X和Y merge。直到同一時候滿足X>Y+Z和Y>Z

因為要合並的兩個 run 是已經排序的,所以合並的時候,有會特別的技巧。如果兩個 runrun1,run2 ,先用 gallopRightrun1 裏使用 binarySearch 查找run2 首元素 的位置k, 那麽 run1k 前面的元素就是合並後最小的那些元素。然後,在run2 中查找run1 尾元素 的位置 len2 ,那麽run2len2 後面的那些元素就是合並後最大的那些元素。最後,依據len1len2 大小。調用mergeLo 或者 mergeHi 將剩余元素合並。

(2.2.5) 反復2.2.1 ~ 2.2.4,直到將待排序數組排序完
(2.2.6) Final Merge:假設此時還有區塊未merge,則合並它們

(3)演示樣例

*註意*:為了演示方便,我將TimSort中的minRun直接設置為2,否則我不能用非常小的數組演示。。

。同一時候把MIN_MERGE也改成2(默覺得32),這樣避免直接進入binary sort。

初始數組為[7,5,1,2,6,8,10,12,4,3,9,11,13,15,16,14]
=> 尋找連續的降序或升序序列 (2.2.1)。同一時候countRunAndMakeAscending 函數會保證它為升序
[1,5,7] [2,6,8,10,12,4,3,9,11,13,15,16,14]


=> 入棧 (2.2.3)
當前的棧區塊為[3]

=> 進入merge循環 (2.2.4)
do not merge由於棧大小僅為1

=> 尋找連續的降序或升序序列 (2.2.1)
[1,5,7] [2,6,8,10,12] [4,3,9,11,13,15,16,14]

=> 入棧 (2.2.3)
當前的棧區塊為[3, 5]

=> 進入merge循環 (2.2.4)
merge由於runLen[0]<=runLen[1]
1) gallopRight:尋找run1的第一個元素應當插入run0中哪個位置(”2”應當插入”1”之後),然後就能夠忽略之前run0的元素(都比run1的第一個元素小)
2) gallopLeft:尋找run0的最後一個元素應當插入run1中哪個位置(”7”應當插入”8”之前),然後就能夠忽略之後run1的元素(都比run0的最後一個元素大)
這樣須要排序的元素就僅剩下[5,7] [2,6],然後進行mergeLow
完畢之後的結果:
[1,2,5,6,7,8,10,12] [4,3,9,11,13,15,16,14]

=> 入棧 (2.2.3)
當前的棧區塊為[8]
退出當前merge循環由於棧中的區塊僅為1

=> 尋找連續的降序或升序序列 (2.2.1)
[1,2,5,6,7,8,10,12] [3,4] [9,11,13,15,16,14]
=> 入棧 (2.2.3)
當前的棧區塊大小為[8,2]


=> 進入merge循環 (2.2.4)
do not merge由於runLen[0]>runLen[1]


=> 尋找連續的降序或升序序列 (2.2.1)
[1,2,5,6,7,8,10,12] [3,4] [9,11,13,15,16] [14]


=> 入棧 (2.2.3)
當前的棧區塊為[8,2,5]


=>
do not merege run1與run2由於不滿足runLen[0]<=runLen[1]+runLen[2]
merge run2與run3由於runLen[1]<=runLen[2]
1) gallopRight:發現run1和run2就已經排好序
完畢之後的結果:
[1,2,5,6,7,8,10,12] [3,4,9,11,13,15,16] [14]


=> 入棧 (2.2.3)
當前入棧的區塊大小為[8,7]
退出merge循環由於runLen[0]>runLen[1]


=> 尋找連續的降序或升序序列 (2.2.1)
最後僅僅剩下[14]這個元素:[1,2,5,6,7,8,10,12] [3,4,9,11,13,15,16] [14]


=> 入棧 (2.2.3)
當前入棧的區塊大小為[8,7,1]


=> 進入merge循環 (2.2.4)
merge由於runLen[0]<=runLen[1]+runLen[2]
由於runLen[0]>runLen[2],所以將run1和run2先合並。(否則將run0和run1先合並)
1) gallopRight & 2) gallopLeft
這樣須要排序的元素剩下[13,15] [14],然後進行mergeHigh
完畢之後的結果:
[1,2,5,6,7,8,10,12] [3,4,9,11,13,14,15,16] 當前入棧的區塊為[8,8]


=>
繼續merge由於runLen[0]<=runLen[1]
1) gallopRight & 2) gallopLeft
須要排序的元素剩下[5,6,7,8,10,12] [3,4,9,11]。然後進行mergeHigh
完畢之後的結果:
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] 當前入棧的區塊大小為[16]


=>
不須要final merge由於當前棧大小為1


=>
結束


參考:

http://www.lifebackup.cn/timsort-java7.html

http://blog.csdn.net/on_1y/article/details/30109975

http://en.wikipedia.org/wiki/Timsort

java.util.ComparableTimSort中的sort()方法簡單分析