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); 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>
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,則將興許的數補足,利用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
java.util.ComparableTimSort中的sort()方法簡單分析