Timsort 算法

轉載自:http://blog.csdn.net/yangzhongblog/article/details/8184707算法

Timsort是結合了合併排序(merge sort)和插入排序(insertion sort)而得出的排序算法,它在現實中有很好的效率。Tim Peters在2002年設計了該算法並在Python中使用(TimSort 是 Python 中 list.sort 的默認實現)。該算法找到數據中已經排好序的塊-分區,每個分區叫一個run,而後按規則合併這些run。Pyhton自從2.3版以來一直採用Timsort算法排序,如今Java SE7和Android也採用Timsort算法對數組排序。數組

內容dom

1 操做ide

   1.1 run的最小長度性能

   1.2 優化run的長度優化

   1.3 合併runui

   1.4 合併步驟this

    1.5 Galloping模型spa

2 性能 .net

 

Timsort的核心過程

       TimSort 算法爲了減小對升序部分的回溯和對降序部分的性能倒退,將輸入按其升序和降序特色進行了分區。排序的輸入的單位不是一個個單獨的數字,而是一個個的塊-分區。其中每個分區叫一個run。針對這些 run 序列,每次拿一個 run 出來按規則進行合併。每次合併會將兩個 run合併成一個 run。合併的結果保存到棧中。合併直到消耗掉全部的 run,這時將棧上剩餘的 run合併到只剩一個 run 爲止。這時這個僅剩的 run 即是排好序的結果。

綜上述過程,Timsort算法的過程包括

(0)如何數組長度小於某個值,直接用二分插入排序算法

(1)找到各個run,併入棧

(2)按規則合併run

 

 

1 操做

 

     現實中的大多數據一般是有部分已經排好序的,Timsort利用了這一特色。Timsort排序的輸入的單位不是一個個單獨的數字,而是一個個的分區。其中每個分區叫一個「run「(圖1)。針對這個 run 序列,每次拿一個 run 出來進行歸併。每次歸併會將兩個 run 合併成一個 run。每一個run最少要有2個元素。Timesor按照升序和降序劃分出各個run:run若是是是升序的,那麼run中的後一元素要大於或等於前一元素(a[lo] <= a[lo + 1] <= a[lo + 2] <= ...);若是run是嚴格降序的,即run中的前一元素大於後一元素(a[lo] >  a[lo + 1] >  a[lo + 2] >  ...),須要將run 中的元素翻轉(這裏注意降序的部分必須是「嚴格」降序才能進行翻轉。由於 TimSort 的一個重要目標是保持穩定性stability。若是在 >= 的狀況下進行翻轉這個算法就再也不是 stable)。

1.1 run的最小長度

    run是已經排好序的一塊分區。run可能會有不一樣的長度,Timesort根據run的長度來選擇排序的策略。例如若是run的長度小於某一個值,則會選擇插入排序算法來排序。run的最小長度(minrun)取決於數組的大小。當數組元素少於64個時,那麼run的最小長度即是數組的長度,這是Timsort用插入排序算法來排序。當數組元素大於等於63時,For larger arrays, a number, referred to as minrun, is chosen from the range 32 to 65, such that the size of the array, divided by the minimum run size, is equal to, or slightly smaller than, a power of two. The final algorithm for this simply takes the six most significant bits of the size of the array, adds one if any of the remaining bits are set, and uses that result as the minrun. This algorithm works for all cases, including the one in which the size of the array is smaller than 64.

 

 

圖1 run

1.2  優化run的長度

       優化run的長度是指當run的長度小於minrun時,爲了使這樣的run的長度達到minrun的長度,會從數組中選擇合適的元素插入run中。這樣作使大部分的run的長度達到均衡,有助於後面run的合併操做。

1.3 合併run

       劃分run和優化run長度之後,而後就是對各個run進行合併。合併run的原則是 run合併的技術要保證有最高的效率。當Timsort算法找到一個run時,會將該run在數組中的起始位置和run的長度放入棧中,而後根據先前放入棧中的run決定是否該合併run。Timsort不會合並在棧中不連續的run(Timsort does not merge non-consecutive runs because doing this would cause the element common to all three runs to become out of order with respect to the middle run.)

Timsort會合並在棧中2個連續的run。X、Y、Z表明棧最上方的3個run的長度(圖2),當同時不知足下面2個條件是,X、Y這兩個run會被合併,直到同時知足下面2個條件,則合併結束:

(1) X>Y+Z

(2) Y>Z

例如:若是X<Y+Z,那麼X+Y合併爲一個新的run,而後入棧。重複上述步驟,直到同時知足上述2個條件。當合並結束後,Timsort會繼續找下一run,而後找到之後入棧,重複上述步驟,及每次run入棧都會檢查是否須要合併2個run。

 

 

 

圖2 合併run

 1.4 合併run步驟

       合併2個相鄰的run須要臨時存儲空閒,臨時存儲空間的大小是2個run中較小的run的大小。Timsort算法先將較小的run複製到這個臨時存儲空間,而後用原先存儲這2個run的空間來存儲合併後的run(圖3)。

 

 

圖3 臨時存儲空間


          簡單的合併算法是用簡單插入算法,依次從左到右或從右到左比較,而後合併2個run。爲了提升效率,Timsort用二分插入算法(binary merge sort)。先用二分查找算法/折半查找算法(binary search)找到插入的位置,而後在插入。
         例如,咱們要將A和B這2個run 合併,且A是較小的run。由於A和B已經分別是排好序的,二分查找會找到B的第一個元素在A中何處插入(圖4)。一樣,A的最後一個元素找到在B的何處插入,找到之後,B在這個元素以後的元素就不須要比較了(圖5)。這種查找可能在隨機數中效率不會很高,可是在其餘狀況下有很高的效率。

 

 

圖4 run合併過程1

 

 

                                                                         圖5 run合併過程2

1.5 Galloping 模型

        介紹的是相似上述run的合併過程,參見維基百科 Galloping Model

2 性能

     根據信息學理論,在平均狀況下,比較排序不會比O(n log n)更快。因爲Timsort算法利用了現實中大多數數據中會有一些排好序的區,因此Timsort會比
O(n log n)快些。對於隨機數沒有能夠利用的排好序的區,Timsort時間複雜度會是log(n!)。下表是Timsort與其餘比較排序算法時間複雜度(time complexity)的比較。

 

 

 

  空間複雜度(space complexities)比較

 

 

說明:

JSE 7對對象進行排序,沒有采用快速排序,是由於快速排序是不穩定的,而Timsort是穩定的。

下面是JSE7 中Timsort實現代碼中的一段話,能夠很好的說明Timsort的優點:

 A stable, adaptive, iterative mergesort that requires far fewer than n lg(n) comparisons when running on partially sorted arrays, while offering performance comparable to a traditional mergesort when run on random arrays.  Like all proper mergesorts, this sort is stable and runs O(n log n) time (worst case).  In the worst case, this sort requires temporary storage space for n/2 object  references; in the best case,  it requires only a small constant amount of space.

大致是說,Timsort是穩定的算法,當待排序的數組中已經有排序好的數,它的時間複雜度會小於n logn。與其餘合併排序同樣,Timesrot是穩定的排序算法,最壞時間複雜度是O(n log n)。在最壞狀況下,Timsort算法須要的臨時空間是n/2,在最好狀況下,它只須要一個很小的臨時存儲空間

相關文章
相關標籤/搜索