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.
相关文章
相关标签/搜索