Timsort
- 前言
- 简介
- 算法
- 限制
- 二分插入排序
- Run
- 合并
- 合并条件
- 合并内存消耗
- 加速合并
- GALLOP模式
- 总结
- 参考
前言
经过60多年的发展,科学家和工程师们发明了很多排序算法,有基本的插入算法,也有相对高效的归并排序算法等,他们各有各的特点,比如归并排序性能稳定、堆排序空间消耗小等等。但是这些算法也有自己的局限性比如快速排序最坏情况和冒泡算法一样,归并排序需要消耗的空间最多,插入排序平均情况的时间复杂度太高。在实际工程应用中,我们希望得到一款综合性能最好的排序算法,能够兼具最坏和最好时间复杂度(空间复杂度的优化可以靠后毕竟内存的价格是越来越便宜),于是基于归并和插入排序的TimSort就诞生了,并且被用作Java和Python的内置排序算法。
简介
Timsort是一个自适应的、混合的、稳定的排序算法,融合了归并算法和二分插入排序算法的精髓,在现实世界的数据中有着特别优秀的表现。它是由Tim Peter于2002年发明的,用在Python这个编程语言里面。这个算法之所以快,是因为它充分利用了现实世界的待排序数据里面,有很多子串是已经排好序的不需要再重新排序,利用这个特性并且加上合适的合并规则可以更加高效的排序剩下的待排序序列。
当Timsort运行在部分排序好的数组里面的时候,需要的比较次数要远小于
nlogn,也是远小于相同情况下的归并排序算法需要的比较次数。但是和其他的归并排序算法一样,最坏情况下的时间复杂度是
O(nlogn)的水平。但是在最坏的情况下,Timsort需要的临时存储空间只有
n/2,在最好的情况下,需要的额外空间是常数级别的。从各个方面都能够击败需要
O(n)空间和稳定
O(nlogn)时间的归并算法。
OK!结合精心制作的动图,让我们来看看这个牛皮的Timsort到底是怎么回事。
算法
限制
在最初的Tim实现的版本中,对于长度小于
二分插入排序
插入排序的逻辑是将排好序的数组之后的一个元素不停的向前移动交换元素直到找到合适的位置,如果这个新元素比前面的序列的最小的元素还要小,就要和前面的每个元素进行比较,浪费大量的时间在比较上面。采用二分搜索的方法直接找到这个元素应该插入的位置,就可以减少很多次的比较。虽然仍然是需要移动相同数量的元素,但是复制数组的时间消耗要小于元素间的一一互换。
比如对于
Run
首先介绍其中最重要的一个概念,英文叫做
比如对于序列
在合并序列的时候,如果
在执行排序算法之前,会计算出这个
这里用Java源码做示范:
1 2 3 4 5 6 7 8 9 | private static int minRunLength(int n) { assert n >= 0; int r = 0; // 如果低位任何一位是1,就会变成1 while (n >= 64) { // 改成了64 r |= (n & 1); n >>= 1; } return n + r; } |
合并
在归并算法中合并是两两分别合并,第一个和第二个合并,第三个和第四个合并,然后再合并这两个已经合并的序列。但是在Timsort中,合并是连续的,每次计算出了一个
在Timsort中用一个栈来保存每个
合并条件
为了保证Timsort的合并平衡性,Tim制定一个合并规则,对于在栈顶的三个
Z>Y+X
Y>X
一旦有其中的一个条件不被满足,
图片来自这里
所谓的合并的平衡性就是为了让合并的两个数组的大小尽量接近,提高合并的效率。所以在合并的过程中需要尽量保留这些
log1.618?N要小的,其中
在最理想的情况下,这个栈从底部到顶部的数字应该是
如果遇到不完美的情况比如
合并内存消耗
不使用额外的内存合并两个
比如有两个相邻的
另外还有一个优化的点在于可以用二分法找到
加速合并
在归并排序算法中合并两个数组就是一一比较每个元素,把较小的放到相应的位置,然后比较下一个,这样有一个缺点就是如果
为了优化合并的过程,Tim设定了一个阈值
在
GALLOP模式
GALLOP搜索元素分为两个步骤,比如我们想找到
第一步是在
(2k?1,2k+1?1)使得
第二步是在第一步找到的范围内通过二分搜索来找到对应的位置。
通过这种搜索方式搜索序列
2lgB次的比较,相比于直接进行二分搜索的
lg(B+1)次比较,在数组长度比较短或者重复元素比较多的时候,这种搜索方式更加有优势。
这个搜索算法又叫做指数搜索(exponential search),在Peter McIlroy于1993年发明的一种乐观排序算法中首次提出的。
总结
总结一下上面的排序的过程:
- 如果长度小于
64 直接进行插入排序 - 首先遍历数组收集每个元素根据特定的条件组成一个
run - 得到一个
run 之后会把他放入栈中 - 如果栈顶部几个的
run 符合合并条件,就会触发合并操作合并相邻的两个run 留下一个run - 合并操作会使用尽量小的内存空间和GALLOP模式来加速合并
参考
Comparison between timsort and quicksort
This is the fastest sorting algorithm ever
TimSort
Timsort: The Fastest sorting algorithm for real-world problems
[Python-Dev] Sorting
Intro
TimSort
更多精彩内容请看我的个人博客