首頁>技術>

寫這張之前我們需要先了解一下TIMSORT

TimSort演算法是一種起源於歸併排序和插入排序的混合排序演算法,設計初衷是為了在真實世界中的各種資料中可以有較好的效能。該演算法最初是由Tim Peters於2002年在Python語言中提出的。

TimSort 是一個歸併排序做了大量最佳化的版本。對歸併排序排在已經反向排好序的輸入時表現O(n2)的特點做了特別最佳化。對已經正向排好序的輸入減少回溯。對兩種情況混合(一會升序,一會降序)的輸入處理比較好。

上一篇文章我們講解了 arrays.sort 裡面的legacyMergeSort 這一篇我們講解最重要的一個演算法ComparableTimSort.sort

    static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {        assert a != null && lo >= 0 && lo <= hi && hi <= a.length;        int nRemaining  = hi - lo;        if (nRemaining < 2)            return;  // 當陣列的大小是 0 或 1 時  陣列總是有序的所以直接返回即可        // 這裡  MIN_MERGE=32        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.         */   // 開始真正的tim sort 過程        ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen);        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;    }

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

開始真正的TimSort過程:

選取minRun大小,之後待排序陣列將被分成以minRun大小為區塊的一塊塊子陣列

    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;    }//這個函式根據 n 計算出對應的 natural run 的最小長度。//MIN_MERGE 預設為32,如果n小於此值,那麼返回n 本身。//否則會將 n 不斷地右移,直到少於 MIN_MERGE,同時記錄一個 r 值,//r 代表最後一次移位n時,n最低位是0還是1。 最後返回 n + r,這也意味著只保留最高的 5 位,再加上第六位。

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

do-while

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

2 若這組區塊大小小於minRun,則將後續的數補足,利用binarySort 對 run 進行擴充套件,並且擴充套件後,run 仍然是有序的。

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

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 是已經排序的,所以合併的時候,有會特別的技巧。假設兩個 run 是 run1,run2 ,先用 gallopRight在 run1裡使用 binarySearch 查詢run2 首元素 的位置k, 那麼 run1 中 k 前面的元素就是合併後最小的那些元素。然後,在run2 中查詢run1 尾元素 的位置 len2 ,那麼run2 中 len2 後面的那些元素就是合併後最大的那些元素。最後,根據len1 與len2 大小,呼叫mergeLo或者 mergeHi 將剩餘元素合併。

5 重複 1 ~ 4,直到將待排序陣列排序完 6 Final Merge:如果此時還有區塊未merge,則合併它們.

38
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Kotin 不錯,但它只不過是C#的模仿者