TimSort——最快的排序算法

news2024/11/16 6:54:14

TimSort——最快的排序算法

排序算法是每个程序员绕不开的课题,无论是大学课程还是日常工作,都离不开排序算法。常见的排序算法有:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、基数排序等。下面是这些算法性能的概览:

算法平均时间复杂度最好情况最差情况空间复杂度排序方式稳定性
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)in-place稳定
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)in-place不稳定
插入排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)in-place稳定
希尔排序 O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)in-place不稳定
归并排序 O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n ) O(n) O(n)out-place稳定
快速排序 O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)in-place不稳定
堆排序 O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( 1 ) O(1) O(1)in-place不稳定
计数排序 O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( k ) O(k) O(k)out-place稳定
桶排序 O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)out-place稳定
基数排序 O ( n k ) O(nk) O(nk) O ( n k ) O(nk) O(nk) O ( n k ) O(nk) O(nk) O ( n + k ) O(n+k) O(n+k)out-place稳定

上述算法中,我们日常用的最多的可能是快速排序和堆排序,这两个算法都是性能很高的排序算法,缺点是不稳定。今天要介绍的 TimSort 算法性能比快速排序和堆排序还高,且是稳定排序算法。

文章目录

    • TimSort 简介
    • TimSort 原理
    • TimSort 详解
      • 小数组用插入排序
      • Run 块
      • 归并
    • 算法实现
    • 总结

TimSort 简介

TimSort 算法是 Tim Peters (就是写 Python 之禅 的那个大神) 于 2001 年为 Python 语言创建的。该算法建立在插入排序和归并排序的基础之上,兼具插入排序和归并排序的优点。

Tim Peters

图1. Tim Peters,就是这位大神开创了 TimSort

TimSort 的平均时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,最好情况 O ( n ) O(n) O(n) ,最差情况 O ( n log ⁡ n ) O(n\log n) O(nlogn) 。空间复杂度 O ( n ) O(n) O(n) ,是一个稳定的排序算法。

算法平均时间复杂度最好情况最差情况空间复杂度排序方式稳定性
快速排序 O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)in-place不稳定
堆排序 O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( 1 ) O(1) O(1)in-place不稳定
归并排序 O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n ) O(n) O(n)out-place稳定
TimSort O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n ) O(n) O(n) O ( n log ⁡ n ) O(n\log n) O(nlogn) O ( n ) O(n) O(n)out-place稳定

自该算法被发明以来,已被 Python、Java、Android 平台和 GNU Octave 用作默认排序算法。Java 中的 Arrays.sort(),Python 中的 sort()sorted() 背后用的都是 TimSort。

TimSort 原理

TimSort 的排序思想并不复杂,首先使用插入排序对小块进行排序,然后使用归并排序合并这些块。

TimSort 会将数组(列表)分成名为 Run 的小块。首先使用插入排序对这些 run 块进行排序,然后使用归并排序中的 combine 函数合并这些 run 块。如果数组的大小小于 run 块的大小,则仅使用插入排序对数组进行排序。run 块的大小可能从 32 到 64 不等,具体取决于数组的大小。请注意,子数组的大小尽量是 2 的幂,这样 merge 函数性能表现会更好。

以下是对 TimSort 算法的逐步解释:

  • 使用插入排序将输入数组分成小的 run块。每个 run 块都为递增顺序。
  • 然后使用改进的归并排序算法合并 run 块。合并步骤的工作原理是比较 每个run 块的第一个元素,并将最小的元素添加到输出数组。该过程一直持续到所有元素都添加到输出数组。
  • 如果 run 块不是良构的,即有些不是按升序排列的,那么它们将合并在一起直到它们为良构的。
  • run 块的大小在每次迭代中增加两倍,直到整个数组排序完成。

TimSort 的想法是基于插入排序对小数组表现良好的事实,因此在最好情况下可以获得插入排序 O ( n ) O(n) O(n) 的最好性能。同时又能获得归并排序最差 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的性能表现。

TimSort 详解

小数组用插入排序

正如前面 TimSort 算法原理讲到的,如果数组比较小(通常是小于 2 6 = 64 2^6=64 26=64),那么 TimSort 会直接采用插入排序对数组进行排序。

这是因为插入排序作为一种简单的排序算法,对小列表最有效。它在较大的列表中非常慢,但在小列表中非常快。 插入排序的思想如下:

  • 逐个扫一遍数组元素
  • 通过在正确位置插入元素来构建排序数组

插入排序相信大家都学过,原理也比较简单,这里不做过多赘述。下面这一张动图很好的演示了插入排序的过程。

img

图2. 插入排序过程演示

Run 块

如果列表比较大,则算法会先遍历列表,查找严格递增或递减的部分。如果该部分是递减的,则将其反转成递增的。

举个例子,假如数组元素是 [ 3 , 2 , 1 , 9 , 17 , 34 ] [3, 2, 1, 9, 17, 34] [3,2,1,9,17,34],遍历发现前3个元素是递减的,则 run 块会将其变为递增的,即 [ 1 , 2 , 3 , 9 , 17 , 34 ] [\bold{1,2,3},9,17,34] [1,2,3,9,17,34]

当 run 块的数量等于或略小于 2 的幂时,合并 2 个数组的效率会更高。Timsort 通过确保 minrun 等于或小于 2 的幂来保证合并的效率。 minrun 的取值范围一般在 32 到 64 之间(含)。选定的 minrun 值要确保原始数组的长度除以 minrun 后等于或略小于 2 的幂。

如果 run 块的长度小于 minrun,则计算该 run 块长度与 minrun 的偏离,看看 run 块还差多少元素并执行插入排序以创建新 run 块。

这部分完成后,我们得到一堆排序好的 run 块。

归并

这一步,Timsort 执行归并排序将 run 块合并在一起。这里,Timsort 需要确保在归并排序时保持稳定性和合并平衡。

为了保持稳定性,算法就不应该交换 2 个等值的数。这不仅保留了它们在列表中的原始位置,而且使算法更快。

当 Timsort 发现 run 块时,会将它们添加到栈中。栈是先进后出的。

Timsort 试图在归并排序 run 块时平衡两种相互竞争的需求。一方面,我们希望尽可能地延迟合并,以便利用稍后可能出现的模式。但我们更希望尽快进行合并,以利用刚刚发现的处于栈顶的 run 块,因此我们也不能将合并延迟“太久”,因为它会消耗更多内存来记住仍未合并的 run 块,并且栈的大小是有限的。

为了找到最优折中方案,Timsort 会跟踪栈中最近的三个项并规定如下 2 个法则:

  1. A > B + C A \gt B+C A>B+C
  2. B > C B \gt C B>C

其中 A , B , C A,B,C A,B,C 是栈中最近的三个项。用 Tim Peters 的原话说:

结果证明这是一个很好的折衷方案,在栈顶维护两个不变量,其中 A、B 和 C 是三个最右边尚未合并的切片的长度。

通常,将不同长度的相邻 run 块合并到位是很困难的。更难的是我们必须保持稳定。为了解决这个问题,Timsort 预留了临时内存。它将两个 run 块中较小的(同时调用 run A 和 run B)放入该临时内存中。

算法实现

下面给出 TimSort 的 Python 实现。

TimSort 依赖于插入排序和归并排序,我们首先实现这 2 种排序。

# insertionSort函数用插入排序从left到right排序数组arr
def insertionSort(arr, left, right):
    for i in range(left + 1, right + 1):
        j = i
        while j > left and arr[j] < arr[j - 1]:
            arr[j], arr[j - 1] = arr[j - 1], arr[j]
            j -= 1
# merge函数合并排序好的run块
def merge(arr, l, m, r):
 
    # 原数组一分为二:左数组和右数组
    len1, len2 = m - l + 1, r - m
    left, right = [], []
    for i in range(0, len1):
        left.append(arr[l + i])
    for i in range(0, len2):
        right.append(arr[m + 1 + i])
 
    i, j, k = 0, 0, l
 
    # 比较后将两个数组合并成一个更大的数组
    while i < len1 and j < len2:
        if left[i] <= right[j]:
            arr[k] = left[i]
            i += 1
 
        else:
            arr[k] = right[j]
            j += 1
 
        k += 1
 
    # 复制左数组遗留元素
    while i < len1:
        arr[k] = left[i]
        k += 1
        i += 1
 
    # 复制右数组遗留元素
    while j < len2:
        arr[k] = right[j]
        k += 1
        j += 1

计算 run 块的最小值,确保归并可以高效运行

MIN_MERGE = 32
 
# 计算run块的最小长度 
def calcMinRun(n):
    r = 0
    while n >= MIN_MERGE:
        r |= n & 1
        n >>= 1
    return n + r

TimSort过程

# TimSort排序
def timSort(arr):
    n = len(arr)
    minRun = calcMinRun(n)
 
    # 对大小为 RUN 的单个子数组进行排序
    for start in range(0, n, minRun):
        end = min(start + minRun - 1, n - 1)
        insertionSort(arr, start, end)
 
    # 从大小 RUN(或 32)开始合并。最终合并形成大小为2^n
    size = minRun
    while size < n:
        # 选择左子数组的起点。 
        # 合并 arr[left..left+size-1] 和 arr[left+size, left+2*size-1] 
        # 每次合并后,left 增加 2*size
        for left in range(0, n, 2 * size):
            # 查找左子数组的终点 
            # mid+1 为右子数组的起点
            mid = min(n - 1, left + size - 1)
            right = min((left + 2 * size - 1), (n - 1))
 
            # 合并子数组 arr[left.....mid] & arr[mid+1....right]
            if mid < right:
                merge(arr, left, mid, right)
 
        size = 2 * size    
if __name__ == "__main__":
    arr = [-2, 7, 15, -14, 0, 15, 0,
           7, -7, -4, -13, 5, 8, -14, 12]
 
    print("排序前")
    print(arr)

    timSort(arr)
 
    print("排序后")
    print(arr)

输出:

排序前
-2, 7, 15, -14, 0, 15, 0, 7, -7, -4, -13, 5, 8, -14, 12
排序后
-14  -14  -13  -7  -4  -2  0  0  5  7  7  8  12  15  15

总结

Timsort 实际上已经内置于 Python 中,上面给出的代码实现仅作为演示用。

在 Python 要使用 Timsort,只需 list.sort()sorted(list) 即可。

如果你想掌握 Timsort 的工作原理,我强烈建议你尝试自己实现一遍!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/593277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022年天府杯全国大学生数学建模竞赛D题高等院校综合发展状况与学科质量评估解题全过程文档及程序

2022年天府杯全国大学生数学建模竞赛 D题 高等院校综合发展状况与学科质量评估 原题再现&#xff1a; 问题背景&#xff1a;   高等院校是我国经济社会发展中的重要参与者&#xff0c;一流大学与一流学科的建成是实现社会主义共同富裕的重要基础。随着国家第二轮“双一流”…

PMP课堂模拟题目及解析(第16期)

151. 一个全球多学科项目的项目经理如何才能确保在所有学科中使用变更控制过程&#xff1f; A. 执行影响分析以考虑所有项目学科 B. 确保变更控制委员会有来自所有学科的代表 C. 邀请所有学科参加变更控制系统委员会会议&#xff0c;但仅从所参加的学科收集意见 D. 代表其…

让你不再好奇图片识别工具怎么用

你是否曾经遇到过想知道一张照片中是什么物品或者景色&#xff0c;却不知道如何搜索的情况&#xff1f;或者&#xff0c;你是否曾经想要识别一张照片中的人脸&#xff0c;想要借助图片识别软件&#xff0c;又不知道图片识别工具怎么用&#xff1f;别担心&#xff0c;接下来我将…

「实在RPA·地产数字员工」筑牢数字经济发展之基

地产行业是国民衣食住行日常生活中的重要组成部分&#xff0c;为人们提供遮风挡雨的温暖港湾。根据国家统计局数据&#xff0c;从GDP占比看&#xff0c;2020年房地产及其产业链占我国GDP的17%&#xff08;完全贡献&#xff09;&#xff0c;是我国经济发展的火车头之一。由此看来…

零代码、低代码、APaaS系统怎么选?一文带你避坑!

零代码、低代码、apaas的概念在整个全球行业内已经流行了很长一段时间。那这3个概念分别指的是什么&#xff1f;他们具备何种能力&#xff1f;企业如果要用该如何选&#xff1f;有哪些好用的低代码平台推荐&#xff1f;下面一文全部给你讲清楚&#xff01; 一、零代码、低代码…

HTML+CSS实训——Day07——发现页用JavaScript进一步修改

仓库链接:https://github.com/MengFanjun020906/HTML_SX 前言 继续改昨天的界面&#xff0c;我们在上周写的都是静态页面&#xff0c;这周开始给静态界面加上javascript&#xff0c;可以让其互相跳转&#xff0c;再加上一些功能。 Find.html // 歌曲列表let songs [{"s…

Java 集合List转 Map 和Map转List的方法总结(举例说明!)

最近遇到了一个场景是&#xff0c;要将从数据库中查出的List<bean>转换为List<map>然后再取map中的value&#xff0c;最后把所有的value写到Excel中。由于这个bean的属性太多了&#xff0c;我最开始没有采用list转map的方式取获取value&#xff0c;而是在for循环中…

结构型设计模式03-外观模式

✨作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 外观模式&#xff08;门面模式&#xff09; 1、外观模式介绍 外观模式&#xff08;Facade Pattern&#xff09;&#xff0c;也称门…

外贸高手揭秘:心理学在谈判中的秘密武器!

在我看来&#xff0c;谈判与心理学密切相关&#xff0c;研究谈判就是研究心理学&#xff01;大学期间&#xff0c;我几乎读完了所有的心理学著作&#xff0c;我必须承认&#xff0c;这为我后来的外贸谈判打下了良好的基础。 有人说:为什么要用谈判这个词&#xff1f;这只是一个…

go语言学习——2.x

文章目录 控制结构if-elseswitchfor(range)break和continue标签与goto 函数参数与返回值传递变长参数defer内置函数递归函数函数作为参数闭包计算函数的执行时间 控制结构 if-else if condition{//do something }if condition{//do something }else{//do something }if condi…

SpringMVC03:Restfule和控制器

目录 一、控制器Controller 二、实现controller接口 三、使用注解Controller 四、RequestMappinng 五、RestFul风格 六、学习测试 1.再新建一个类RestfulController 2.在SPring MVC种可以使用PathVariable注解&#xff0c;让方法参数的值对应绑定到一个URI模板变量上。…

【hello C++】类和对象(中)

目录 1. 类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3. 析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5.赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 5.3 前置和后置重载 7.const成员 8.取地址及const取地址操作符重载 类和对象&#x1f337…

《Java并发编程实战》课程笔记(六)

管程&#xff1a;并发编程的万能钥匙 什么是管程 Java 采用的是管程技术&#xff0c;synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。 管程和信号量是等价的&#xff0c;所谓等价指的是用管程能够实现信号量&#xff0c;也能用信号量实…

通用AppKey签名验证软件

一、 需求说明 签名验证是一种技术&#xff0c;用于确保数据完整性和身份验证。在Java应用程序中&#xff0c;签名通常是由开发人员提供的一个字符串&#xff0c;它基于请求的内容和一些密钥信息生成。这个签名可以被认为是一种指纹&#xff0c;它唯一地标识了请求的内容&…

BLECommonTool通用测试工具介绍

工具下载地址&#xff1a;BLECommonTool通用工具资源-CSDN文库 大家在使用过程中&#xff0c;如有发现bug或有更好的建议&#xff0c;欢迎留言或发我QQ邮箱&#xff1a;1255033066qq.com. 工具界面 以下是关于GMBLETool工具的详细使用说明&#xff1a; 蓝牙适配器状态检测&…

springcloudAlibaba整合knife4j整合swagger整合gateway,并且同步到Yapi上

springcloudAlibaba整合knife4j整合swagger整合gateway&#xff0c;并且同步到Yapi上 1.gateway模块 1.pom引入 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version&g…

什么是七专,电子元器件分级详细总结

🏡《电子元器件高级指南》 目录 0,概述1,商业级元器件2,工业级元器件3,汽车工业级元器件4,七专级元器件5,军级元器件6,航天级元器件0,概述 随着科学技术的发展,以及集成电路技术的不断进步。电子元器件的应用越来越广泛。在不同的应用场景下,往往需要不同等级的电子…

(双指针 ) 15. 三数之和 ——【Leetcode每日一题】

❓15. 三数之和 难度&#xff1a;中等 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a…

pip安装python库速度慢、失败及超时报错解决办法

背景&#xff1a; 随着人工智能的不断兴起&#xff0c;python作为最接近人工智能的语言&#xff0c;变得越来越流行&#xff0c;人生苦短&#xff0c;python要学起来。之所以越来用的人喜欢学习python和研究Python&#xff0c;除了python本身便于学些、语法简短、面向对象等特点…

静态库与动态库的区别

静态库与动态库的区别 静态库动态库 首先用OpenCV的开源库来举个例子了解一下库文件的分类&#xff1a; bin文件夹里面放的都是dll文件&#xff1b; lib文件夹里面放的都是伴随dll文件的动态lib文件&#xff1b; staticlib文件夹里面放的才是真正的静态lib文件&#xff0c;和…