作者丨Brandon Skerritt

译者丨阿拉丁

策划丨蔡芳芳

知道 Java 和 Python 的默认排序算法是什么吗?这个算法叫作 Timsort,由 Tim Peters 与 2001 年创建,是一种稳定高效的面向真实数据的排序算法。

Timsort 是一种面向真实数据的高效排序算法,它不是在学术实验室中创建出来的。2001 年,Tim Peters 为 Python 创建了 Timsort 算法。Timsort 算法首先对排序数据进行分析,然后根据分析结果来选择排序方式。

在该算法出现之后,就一直被作为 Python、Java、Android 平台和 GNU Octave 的默认排序算法。

Timsort 的时间复杂度是 O(n log n)。关于时间复杂度,可以参考下图。

Timsort 的排序时间与归并排序相同。实际上,Timsort 使用了插入排序和归并排序,你很快就会看到。

Timsort 利用了存在于大多数真实数据集中的已排序元素,这些已排序的元素被称为“natural runs”。算法会遍历要排序的数据,将元素收集到 run 中,同时将这些 run 合并在一起。

元素个数少于 64 的数组

如果数组的元素个数少于 64,Timsort 将执行插入排序。

插入排序是一种对小数据集最为有效的排序算法。它在排序较大的数据集时速度很慢,但在排序较小的数据集时速度很快。插入排序的思想如下:

逐个检查元素;

在正确的位置插入正确的元素,以此来构建排好序的列表。

在这个示例中,我们将新排序的元素插入到一个新的子数组中,从数组的头部开始。

插入排序的 GIF 动画演示:

关于 run

如果列表的元素个数大于 64,Timsort 会先遍历列表,查找严格递增或递减的部分。如果这个部分是递减的,就反转这个部分。

所以,如果 run 是递减的,它看起来像这样(其中 run 使用粗体表示):

如果不是递减的,它看起来是这样的:

minrun 根据数组的大小来确定。当 run 的数量等于或略小于 2 的幂时,合并 2 个数组的效率会更高。为了确保效率,Timsort 选择的 minrun 通常等于或小于 2 的幂。

minrun 的取值范围为 32 到 64。原始数组的长度除以 minrun 仍然等于或略小于 2 的幂。

如果 run 的长度小于 minrun,就用 minrun 减去这个 run 的长度,得出一个数字,然后针对这个数字长度的元素执行插入排序,以此来创建新的 run。

所以,如果 minrun 是 63,run 的长度是 33,那么就是 63-33=30,也就是对 run[33] 前的 30 个元素执行插入排序来创建新的 run。

在这部分完成之后,我们就有了一系列排好序的 run。

合并

接下来,Timsort 会执行归并排序,将 run 合并在一起。Timsort 在执行合并排序时会确保稳定性和均衡性。

为了确保稳定性,我们不需要交换两个相等的数字。这样不仅可以保持它们在列表中的原始位置不动,而且会让算法更快。我们很快就会解释合并的均衡性问题。

在遇到一个 run 时,Timsort 将它添加到栈中。一个简单的栈看起来像这样:

你可以想象面前有一叠盘子,但你不能从底部拿盘子,必须从顶部拿。栈也是这个原理。

Timsort 在合并 run 时会尝试平衡两个相互矛盾的需求。一方面,我们希望尽可能多地推迟合并,以便找出可能会出现的模式,但也更希望尽快地进行合并,以便利用刚刚发现的 run。但我们不能延迟合并“太久”,因为我们需要记住未合并的 run,而这样做会消耗更多的内存。

为了获得这种平衡,Timsort 会跟踪栈上最新的三个项,这些项必须遵循以下规则:

A > B + C

B > C

其中 A、B 和 C 是栈上最新的三个项。

通常,合并不同长度的相邻的 run 是很困难的,而更困难的是我们必须确保稳定性。为了解决这个问题,Timsort 会留出临时内存,将较小的 run 放到临时内存中。

“奔驰”模式

Timsort 在合并 run A 和 run B 时会发现其中一个 run 连续多次“获胜”。如果 run A 的数字都比 run B 的数字小,那么 run A 的数字最后会待在原来的位置上。合并这两个 run 需要做大量的工作,但并没有产生任何结果。

排序的数据通常会有一些预先存在的内部结构。Timsort 假设如果 run A 的多个值小于 run B 的值,那么很可能 A 的值都小于 B 的值。

在示例中,run A 和 run B 必须严格递增或递减。

Timsort 将进入奔驰(gallop)模式。Timsort 不检查 A[0] 和 B[0] 之间的相互关系,而是进行二分查找,以便找出 B[0] 在 A[0] 中的位置。这样,Timsort 就可以将 A 的整个部分移动到合适的位置。然后,Timsort 在 B 中搜索 A[0] 的位置,再将 B 的整个部分移动到适当的位置。

让我们来看看它是如何运行的。Timsort 检查 B[0](即 5),并使用二分查找找出它在 A 中的位置。

可以看到,B[0] 在 A 的尾部。然后,Timsort 检查 A[0](即 1)在 B 中的位置。可以看到,数字 1 在 B 的开头。我们现在知道 B 在 A 的尾部,A 在 B 的开头。

事实证明,如果 B[0] 的位置非常接近 A 的开头,那这么做就不值得,反之亦然。如果是这种情况,它就会快速退出奔驰模式。此外,Timsort 会把这个记下来,并通过增加连续 A 或连续 B 的数量来提高进入奔驰模式的门槛。如果进入奔驰模式是值得的,Timsort 会让重新进入奔驰模式变得容易些。

总的来说,Timsort 有两个方面做得非常好:

对存在内部结构的数组排序有非常好的性能

能够保持稳定的排序

在以前,为了实现稳定的排序,必须将列表中的项映射成整数,并将其作为元组数组进行排序。

代码

如果你对代码不感兴趣,请跳过这一部分。

Timsort 的原始源代码:

https://github.com/python/cpython/blob/master/Objects/listobject.c 。

Python 已经内置了 Timsort 算法,要使用 Timsort,只需这样写:

或者:

如果你想掌握 Timsort 算法,我强烈建议你自己试着实现它!

https://skerritt.blog/timsort-the-fastest-sorting-algorithm-youve-never-heard-of/

点个在看少个 bug

java什么是稳定排序,这可能是你听说过最快的稳定排序算法相关推荐

  1. java编写排序的代码_在Java 8之前,您编写了几行代码来对对象集合进行排序?...

    java编写排序的代码 在Java 8之前,您编写了几行代码来对对象集合进行排序? Java 8您需要多少个? 您可以在Java 8中用一行完成. 让我们看看下面的Employee类. public ...

  2. 在Java 8之前,您编写了几行代码来对对象集合进行排序?

    在Java 8之前,您编写了几行代码来对对象集合进行排序? Java 8您需要多少个? 您可以在Java 8中用一行完成. 让我们看看下面的Employee类. public class Employ ...

  3. java中list里面存放map,根据map中的某一个字段进行排序

    Java中list里面存放map,根据map中的某一个字段进行排序 例如: [java] view plaincopy package com; import java.util.ArrayList; ...

  4. java treemap value排序_Java TreeMap升序|降序排列和按照value进行排序的案例

    TreeMap 升序|降序排列 import java.util.Comparator; import java.util.TreeMap; public class Main { public st ...

  5. java升序降序排列desc,TreeMap升序|降序排列和按照value进行排序

    TreeMap 升序|降序排列 import java.util.Comparator; import java.util.TreeMap; public class Main { public st ...

  6. Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day28,字节跳动算法工程师面试

    车票 面试题1:说一下你对聚集索引与非聚集索引的理解,以及他们的区别? 追问1:为什么聚集索引可以创建在任何一列上,如果此表没有主键约束,即有可能存在重复行数据呢? 追问2:聚集索引一定比非聚集索引性 ...

  7. python选择排序从大到小_经典排序算法和Python详解之(一)选择排序和二元选择排序...

    本文源自微信公众号[Python编程和深度学习]原文链接:经典排序算法和Python详解之(一)选择排序和二元选择排序,欢迎扫码关注鸭! 扫它!扫它!扫它 排序算法是<数据结构与算法>中最 ...

  8. 9个元素换6次达到排序序列_程序员必须掌握的:10大排序算法梳理已整理好

    从数组中选择最小元素,将它与数组的第一个元素交换位置.再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置.不断进行这样的操作,直到将整个数组排序. 动态过程 算法原理参考:图解选择排 ...

  9. arraylist从大到小排序_程序猿面试宝典:你该知道的数组排序算法

    通常情况下,我们对数组的操作远远不止遍历判断大小或者判断奇偶数这么简单.比如,当我们需要求一个数组中所有元素的平均值时,操作很简单,只需要去遍历这个数组,并将其内部所有元素中存储的内容进行求和,最后用 ...

最新文章

  1. AI一分钟 | NLP先驱Aravind Joshi教授去世,曾获ACL终身成就奖;年度花木兰诞生,甘薇全权负责贾跃亭的一地鸡毛
  2. 机器学习(MACHINE LEARNING)MATLAB非线性曲线拟合方法
  3. 数学建模大赛赛题解析:Mathorcup高校数学建模挑战赛-基于收得率预测模型的转炉炼钢的成本优化
  4. [ZJOI2007]棋盘制作
  5. C++中的指针与引用
  6. 使用git checkout命令切换到指定的commit
  7. unity获取ugui上鼠标位置
  8. android发布新版忘记keystore(jks)密码终极解决方案
  9. PowerDesigner的使用
  10. 计算机网络五层模型详解
  11. Web课程设计-仿当当网-增删改查-java+jsp+mysql-期末大作业
  12. Win10安装WSL-Ubuntu18.04
  13. HTML语言分栏左右比例怎么调整,wps怎么设置分栏排版?
  14. Linux资源控制-使用cgroup控制CPU和内存
  15. 第一性原理计算筛选本征二维磁性材料
  16. io密集服务器cpu性能,线程池中CPU密集型和IO密集型选择
  17. 棋类对战小游戏(VS2012 MFC基于对话框)
  18. hfai images | 自建镜像,环境配置的最后绝招
  19. 一个简单入门的Py笔记
  20. 短信、微信、QQ信息监听

热门文章

  1. 各个级别镜像之间的跳转模型
  2. C语言获取数组越界,除以零等异常
  3. CreateThread和CreateRemoteThread
  4. 2020-10-26(对Dex文件的理解)
  5. 8、 IS NULL:空值查询
  6. 超详细!各种内部排序算法的比较
  7. Python中list、set和tuple
  8. 实验:交换机生成树协议STP--功能验证
  9. jQuery的效果方法
  10. Tomcat在自定义xml文件中配置虚拟目录