赞
踩
当在使用python中自带的排序算法、或者Java中的排序算法时,产生了一些好奇,他们本身运用的是什么高端的排序算法,深究、探索、查阅资料后得到了如下的认识。
Timsort是一种混合、稳定高效的排序算法,源自合并排序和插入排序,旨在很好地处理多种真实数据。它由Tim Peters于2002年实施使用在Python编程语言中。该算法查找已经排序的数据的子序列,并使用该知识更有效地对其余部分进行排序。这是通过将已识别的子序列(称为运行)与现有运行合并直到满足某些条件来完成的。从版本2.3开始,Timsort一直是Python的标准排序算法。如今,Timsort 已是是 Python、 Java、 Android平台 和 GNU Octave 的默认排序算法。
针对现实中需要排序的数据分析看,大多数据通常是有部分已经排好序的数据块,Timsort 就利用了这一特点。Timsort 称这些已经排好序的数据块为 “run”,我们可以将其视为一个一个的“分区”。在排序时,Timsort迭代数据元素,将其放到不同的 run 里,同时针对这些 run ,按规则进行合并至只剩一个,则这个仅剩的 run 即为排好序的结果。
换句话说,就是分析待排序数据,根据其本身的特点,将排序好的(不管是顺序还是逆序)子序列的分为一个个run分区,当然,这个分区run也存在一定的约束,即根据序列会产生一个minrun,如果原始的run小于minrun的长度,用插入排序扩充run,直到达到条件,之后使用归并排序来合并多个run。
在 Galloping mode 中,算法在一个 run 中搜索另一个 run 的第一个元素。通过将该初始元素与另一个 run 的第
2
k
−
1
2k-1
2k−1个元素(即1,3,5…)进行比较来完成的,以便获得初始元素所在的元素范围。这缩短了二分查找的范围,从而提高了效率。如果发现 Galloping 的效率低于二分查找,则退出 Galloping mode。
例如,我们要将 X 和 Y 这2个 run 合并,且X是较小的 run,以及 X 和 Y 已经分别是排好序的,将X复制到cache memory中,如下图所示。
二分查找会找到 X 中第一个大于 Y[0] 的元素 x,当找到 x 时,可以在合并时忽略 x 之前的元素。类似的,还可以在 Y 中找到第一个大于 X[-1] (即X最大的元素)的元素 y,当找到 y 时,可以在合并时忽略 y 之后的元素,这种查找可能在随机数中效率不会很高,但是在其他情况下有很高的效率。
当算法到达最小阈值min_gallop时,算法切换到 Galloping mode,试图利用数据中的那些可以直接排序的元素。只有当一个 run 的初始元素不是另一个 run 的前七个元素之一时,Galloping 才有用。这意味着初始阈值为7。
为了避免 Galloping mode 的缺点,合并函数会调整阈值。如果所选元素来自先前返回元素的同一数组,则min_gallop减1。否则,该值增加1,从而阻止返回到 Galloping mode 。 在随机数据的情况下,min_gallop的值会变得非常大,以至于 Galloping mode 永远不会再次发生。
本质上 Timsort 是一个经过大量优化的归并排序,而归并排序已经到达了最坏情况下,比较排序算法时间复杂度的下界,所以在最坏的情况下,Timsort 时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。在最佳情况下,即输入已经排好序,它则以线性时间运行
O
(
n
)
O(n)
O(n)。可以看出Timsort是目前最好的排序方式。
以下实现的代码并不是具体完整的timsort,而是简化的,符合Timsort思想的大概实现。具体、完整的代码可以参看python源代码中的排序,或者JDK1.8中的源码,也可参照Timsort官方原始C代码:
#!/usr/bin/env python # coding=utf-8 """realize timsort""" __author__ = 'steven' def binary_search(arr, left, right, value): """ 二分查找 :param arr: 列表 :param left: 左索引 :param right: 右索引 :param value: 需要插入的值 :return: 插入值所在的列表位置 """ if left >= right: if arr[left] <= value: return left + 1 else: return left elif left < right: mid = (left + right) // 2 if arr[mid] < value: return binary_search(arr, mid + 1, right, value) else: return binary_search(arr, left, mid - 1, value) def insertion_sort(arr): """ 针对run使用二分折半插入排序 :param arr: 列表 :return: 结果列表 """ length = len(arr) for index in range(1, length): value = arr[index] pos = binary_search(arr, 0, index - 1, value) arr = arr[:pos] + [value] + arr[pos:index] + arr[index + 1:] return arr def merge(l1, l2): """ 合并 :param l1: 列表1 :param l2: 列表2 :return: 结果列表 """ if not l1: return l2 if not l2: return l1 if l1[0] < l2[0]: return [l1[0]] + merge(l1[1:], l2) else: return [l2[0]] + merge(l1, l2[1:]) def timsort(arr): """ timsort :param arr: 待排序数组 :return: """ if not arr: # 空列表 return runs, sorted_runs = [], [] new_run = [arr[0]] length = len(arr) # 划分run区,并存储到runs里,这里简单的按照升序划分,没有考虑降序的run for index in range(1, length): if arr[index] < arr[index - 1]: runs.append(new_run) new_run = [arr[index]] else: new_run.append(arr[index]) if length - 1 == index: runs.append(new_run) break # 由于仅仅是升序的run,没有涉及到run的扩充和降序的run # 因此,其实这里没有必要使用insertion_sort来进行run自身的排序 for run in runs: insertion_sort(run) # 合并runs sorted_arr = [] for run in runs: sorted_arr = merge(sorted_arr, run) print(sorted_arr)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。