java.util.ComparableTimSort中的sort()方法简单分析
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);elseComparableTimSort.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 mergesif (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 runint 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 mergets.pushRun(lo, runLen);ts.mergeCollapse();// Advance to find next runlo += runLen;nRemaining -= runLen;} while (nRemaining != 0);// Merge all remaining runs to complete sortassert 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
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,則將興許的數補足,利用binarySort 對 run 進行擴展。而且擴展后,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 是已經排序的,所以合并的時候,有會特別的技巧。如果兩個 run 是 run1,run2 ,先用 gallopRight在 run1 里使用 binarySearch 查找run2 首元素 的位置k, 那么 run1 中 k 前面的元素就是合并后最小的那些元素。然后,在run2 中查找run1 尾元素 的位置 len2 ,那么run2 中 len2 后面的那些元素就是合并后最大的那些元素。最后,依據len1 與len2 大小。調用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
轉載于:https://www.cnblogs.com/lxjshuju/p/7081959.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的java.util.ComparableTimSort中的sort()方法简单分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Math常用方法,String转floa
- 下一篇: 金字塔原理(Pyramid Princi