Timsort

 
Timsort是一種混合排序算法,由merge sort和insertion sort衍生而來。這個算法查找已經排序好的子集,而且利用這些來有效的排序其它數據。這個過程經過歸併一個已知子集,稱爲run,和當前存在的run,直到特定區域被填滿。
最壞時間O(nlogn)
最好時間O(n)
平均時間O(nlogn)
最壞空間O(n)
 
操做
--------------------------------------------------------------------------------------------------------------------
Timsort的主要思路是利用現實世界中不少已經局部有序的數據的特色。TS經過尋找runs,至少兩個元素的子集。Runs或者是非降序的,或者是嚴格降序的。若是降序必須是嚴格降序的,降序的runs以後將會經過簡單的交換元素,從兩端向中間聚合。若是是嚴格降序的,那麼這個方法是穩定的。主要流程描述:
  • 一個特別的算法用來將輸入隊列劃分爲子隊列
  • 每一個子隊列用簡單的插入排序
  • 排序好的子隊列用merge sort聚合進一個隊列
 
算法描述
===================================================
定義
-----------------------------------------------------------------------------------------
  • N:輸入隊列大小
  • run:一個有序的子隊列,子隊列的順序是嚴格降序或者非降序。例如:「a0<=a1<=a2...」 or "a0>a1>a2..."
  • minrun:正如上面描述的,算法的第一步是將輸入隊列劃分紅runs,minrun是這個run的最小長度,這個數字是經過N來計算出來的。
                     
Step 0 計算Minrun
-------------------------------------------------------------------------------------------------------------------------------------
minrun是由N來決定的,依據如下規則:
  • 不能太長,由於minrun只會會被用插入排序處理,短一點效率高。
  • 不能過短,run越短,越多的runs要在下一步被merge。
  • 若是N/minrun是2的冪最好(接近),由於merge排序在runs長度相同的時候效率最高。
    這裏做者給出了實驗,若是minrun > 256,規則1違背,minrun<8,規則2違背,最好的長度是32到65.例外:若是N<64,minrun=N,Timsort退化成merge sort。minrun計算很簡單,取最高六位,如該剩下的位包含1,那麼再加個1,代碼以下:
int GetMinrun(int n) {
              int r = 0; /* becomes 1 if the least significant bits contain at least one off bit */
              while (n >= 64) {
                     r |= n & 1;
                     n >>= 1;
             }
     return n + r;    
}
 
 
Step 1 Splitting into Runs and Their Sorting
----------------------------------------------------------------------------
     到如今爲止,有一個長度爲N的輸入隊列和一個計算好的minrun,算法流程以下:

  • 當前元素基址設定爲輸入隊列開始
  • 從當前隊列開始,在輸入隊列中查找run(有序子隊列)。根據定義,run將會至少包括當前元素和隨後的一個,但再日後的就要看運氣。若是最終隊列是降序的,那麼元素將會是非降序順序(將兩端的數據向中間移動,交換元素)
  • 若是當前run比minrun小,那麼取當前run以後的minrun-size(run)個元素。最終的run大小是minrun或者更多,一部分是排序的(理想狀態下全部)
  • 對run執行插入排序。由於這個run是小的而且局部有序,插入很快,高效。
  • 移動基址到當前run後
  • 若是當前輸入隊列未達到末尾,繼續執行過程2。
Step2  Merge     
-------------------------------------------------------------------------------------------------
    到目前爲止,input隊列被分爲runs。如該輸入隊列接近隨機,run大小都會接近minrun,如該數據是範圍有序,run大小超過minrun。如今runs須要被結合來完成排序,固然,兩個要求要知足:
  • 被結合的run的大小要儘可能一致,這樣更高效
  • 算法的穩定性要保證,沒有沒必要要偏移,例如兩個連續的相等的數,不能交換位置
這個能夠用這種方式來實現:
  • 建立空的pair stack<run base address>-<run size>,取第一個run
  • 添加一些數據到棧<base address>-<size>到當前run
  • 評估一下當前的run是否應該被合併到前一個。檢查以下兩個條件是否知足:X,Y,Z是棧頂的三個run:X>Y+Z Y>Z
  • 若是其中一個條件不知足,那麼Y就和X,Z中較小的隊列進行合併。這個過程一直進行知道兩個條件都知足或者全部數據都有序
  • 對應任何一個沒被考慮的run,重複第二步
    這樣run就適合merge排序。這樣的方式merge更加平衡。
 
Runs Merging
------------------------------------------------------------------------------------------------------------------------------------------
    merge過程須要額外的內存,這個方法最後利用原來的兩個run的內存來存儲數據
  • 一個臨時的run被建立,大小是要merge run中最小的
  • 將最短的run拷貝到臨時run
  • 定位到較大的和臨時的兩個run隊列
  • 對於每一個,用如下方法比較,將較小的移動到一個新的排序隊列,移動元素指針到下一個元素
  • 重複第四步直到其中一個取空
  • 將剩下的run中的元素加入到新隊列
 
Modifications to the Merging Sort
---------------------------------------------------------------------------------------------------------------------------------------------
    基本方法是,若是發現一個隊列比另外一個大好多,那麼切換模式,進入galloping模式,批量移動。

All seems perfect in the above merge sort. Except for one thing: imagine the merge of such two arrays:算法

A = {1, 2, 3,..., 9999, 10000}less

B = { 20000, 20001, ...., 29999, 30000}this

The above procedure will work for them too, but at each step four, one comparison and one moving should be performed to give 10000 comparisons and 10000 moves. Timsort offers here a modification called galloping. It means the following:指針

  1. Start the merge sort as described above.
  2. At each moving of an element from the temporary or large run to the final one, the run where the element was moved from is recorded.
  3. If a number of elements (in this representation of the algorithm this number equals 7) was moved from one and the same run, it can be assumed that the next element will also come from the same run. To prove it, the galloping mode is switched, i.e. go through the run that is expected to supply the next set of data with a binary search (remember that the array is ordered and we have all rights for binary search) to find the current element from the other merged run. The binary search is more efficient than the linear one, thus the number of searches will be much smaller.
  4. Finally, when the data in the supplying run do not fit (or having reached the run end), this data can be moved in a bulk (which can be more efficient than moving separate elements).

The explanation can be a bit vague, so let’s have a look at the example: A = {1, 2, 3,..., 9999, 10000} B = { 20000, 20001, ...., 29999, 30000}orm

  1.  In the first seven iterations, numbers 1, 2, 3, 4, 5, 6 and 7 from run A are compared with number 20000 and, after 20000 is found to be greater, they are moved from array A to the final one.
  2. Starting with the next iteration, the galloping mode is switched on: number 20000 is compared in sequence with numbers 8, 10, 14, 22, 38, n+2^i, ..., 10000 from run A. As you can see, the number of such comparisons will be far less than 10000.
  3. When run A is empty, it is known that it is smaller than run B (we could also have stopped somewhere in the middle). The data from run A is moved to the final one, and the procedure is repeated.
相關文章
相關標籤/搜索