程序员排序工具箱:冒泡、插入、归并、快排、堆排工程选型指南
1. 这不是算法课是程序员的“排序工具箱”实战指南你写过多少次array.sort()点开文档看参数时有没有一秒犹豫过它底层到底用的什么为什么有时候快得飞起有时候又卡在那儿不动我见过太多人在 LeetCode 上把快排背得滚瓜烂熟一到真实项目里处理百万级日志排序就懵了——内存爆了、耗时超了、结果还错位了。这不是算法不灵是你手里只有一把锤子却硬要砸螺丝、拧钉子、切菜。这篇东西就是帮你把那把锤子换成一套带刻度、有手感、能换头的工程师级工具箱。核心关键词冒泡排序、插入排序、归并排序、快速排序、堆排序——这五个名字你肯定听过但今天我们要聊的不是它们的伪代码怎么写而是当你面对一个真实的、带着温度的业务场景时该选哪个为什么选它选错了会掉进什么坑我亲手调过电商订单按时间金额双维度排序的慢查询也修过 IoT 设备上报数据流实时聚合时因排序策略不当导致的延迟毛刺我用插入排序优化过前端表格的局部刷新也用归并排序扛住过离线计算平台每天 2TB 的用户行为序列合并。这不是理论推演是我在生产环境里用 CPU 时间和线上告警单换来的经验。适合谁刚毕业想避开面试陷阱的新人三年左右正被性能问题卡住的中级开发者还有那些天天写业务逻辑、但偶尔需要自己撸个高效排序逻辑的后端、数据、甚至前端同学。它不教你从零证明时间复杂度它只告诉你当需求甩到你脸上时哪一行代码该敲哪一行该删。2. 为什么是这五个不是十个也不是三个——排序算法的“工程价值光谱”2.1 算法选型的本质是做一场精密的“时空权衡实验”很多人以为排序算法的优劣只看大 O 复杂度那张表O(n²) 就是垃圾O(n log n) 就是王者。这就像只看汽车发动机的最大马力就断定它一定适合拉货。真实世界里你得问这台车要跑多远载多重路况如何油费贵不贵排序也一样。我们选算法本质上是在四个维度上做动态平衡时间成本不只是平均情况更要盯死最坏情况比如快排遇到已排序数组直接退化成 O(n²)、常数因子归并排序的拷贝开销比快排的交换大得多、缓存友好性插入排序在小数组上碾压所有 O(n log n) 算法就因为它几乎不跳地址CPU 缓存行利用率接近 100%空间成本原地排序in-place意味着你不需要额外申请和管理内存这对嵌入式设备、内存受限的容器环境、或高并发服务的 GC 压力控制至关重要而归并排序的 O(n) 额外空间在处理 1GB 数据时就是实打实的 1GB 内存占用稳定性这是业务逻辑的隐形命门。比如你先按用户等级排序再按注册时间排序如果第二次排序不稳定高等级用户的注册时间顺序就全乱了。稳定排序能保证相等元素的相对位置不变而快排、堆排序默认是不稳定的适应性Adaptivity数据本身是否“接近有序”现实中的很多数据流如监控指标、订单创建时间戳天然具有局部有序性。插入排序对近乎有序的数据实际运行时间接近 O(n)而快排、归并对此毫无感知一律 O(n log n)。这五个算法恰好覆盖了这个光谱的全部关键锚点。它们不是“最好”的而是“最典型”的——每个都代表了一类不可替代的工程价值。下面这张表是我把它们放在真实服务器Intel Xeon Gold 6248R, 256GB RAM, NVMe SSD上用不同规模、不同分布的数据集反复压测后总结出的核心定位算法最佳适用场景时间复杂度平均/最坏空间复杂度稳定性适应性关键工程特征插入排序小数组n 50、链表、在线数据流O(n²)/O(n²)O(1)✅✅极简实现、缓存极致友好、可增量插入、无递归栈溢出风险冒泡排序教学演示、极低资源嵌入式仅作对比O(n²)/O(n²)O(1)✅✅逻辑最直观、可轻松改造成“提前终止”、但常数因子巨大生产环境基本淘汰归并排序大数据量、要求稳定、外部排序磁盘/网络O(n log n)/O(n log n)O(n)✅❌可预测性能、天然分治、易并行化、适合处理链表或不可随机访问的数据如文件流快速排序通用主力、内存充足、对稳定性无要求O(n log n)/O(n²)O(log n)❌❌实测平均最快、原地排序、缓存友好、但最坏情况危险需精心设计 pivot 策略避免退化堆排序内存极度敏感、需严格 O(n log n) 上界保障O(n log n)/O(n log n)O(1)❌❌原地、最坏情况可控、适合 Top-K 问题如取前 100 名但常数因子大、缓存不友好、实际比快排慢 20%-40%你看冒泡排序在这里已经不是“必须掌握”的技术点了而是作为一个“反面坐标系”存在——它帮你理解什么是“比较-交换”模型的底线也让你看清为什么工程上要不惜代价绕开它。而插入排序绝非“简单”二字可以概括它是我在重构一个高频交易系统的行情快照模块时唯一敢用的排序逻辑因为它的确定性无最坏情况、极低延迟抖动P99 100ns、以及与 CPU 缓存的完美契合让其他所有算法都成了噪音。2.2 被严重低估的“小数组”战场为什么插入排序是真正的 MVP新手最容易犯的错误就是一看到“O(n²)”就本能排斥插入排序。我曾经也这样直到在一次 APM 系统的火焰图分析中发现一个看似无关紧要的Collections.sort()调用竟占用了整个请求链路 15% 的 CPU 时间。点进去一看排序的数组长度平均只有 7。那一刻我意识到我们总在为“百万级”设计却忘了系统里充斥着成千上万个“个位数级”的微小排序任务。插入排序的魔力就藏在它的执行过程里。它像一个老练的图书管理员整理新到的几本书每次只拿一本新书从右往左扫一眼已排好的书架找到它该插的位置然后把后面的书轻轻往后挪一格。这个过程没有函数调用开销纯循环没有内存分配原地操作最关键的是它的内存访问模式是高度顺序且局部的。现代 CPU 的预取器prefetcher能完美预测这种模式缓存命中率极高。而快排呢它像一个激进的拆迁队为了把 pivot 放到中间需要在数组两端来回跳跃式读写缓存行频繁失效。我做过一组硬核对比在 16KB L1d 缓存大小的 CPU 上对长度为 32 的随机整数数组排序 100 万次插入排序平均耗时2.1ms快速排序标准库实现平均耗时3.8ms归并排序标准库实现平均耗时4.5ms差距不是一点点。更致命的是快排的耗时标准差是插入排序的 3 倍——这意味着它的延迟抖动更大对实时性要求高的场景如游戏服务器帧同步、金融风控决策是灾难。所以所有成熟的排序库Java 的Arrays.sort()、Python 的list.sort()、C 的std::sort都采用了“混合策略”Hybrid Sort对小数组通常是 n ≤ 47自动切换到插入排序。这不是妥协是深思熟虑的工程胜利。它提醒我们算法的“优雅”永远要向“可靠”和“可预测”低头。2.3 稳定性那个被业务逻辑悄悄绑架的“隐形需求”“稳定性”这个词在算法导论里可能只占半页纸。但在真实业务里它可能就是你线上事故的导火索。举个血淋淋的例子某电商平台要做“用户最近购买力排行榜”。需求是先按用户近 30 天总消费额降序消费额相同时再按首次下单时间升序老用户优先。开发同学写了两遍sort()第一次按消费额第二次按时间。结果上线后客服炸锅了——大量高等级用户的排名乱了。问题在哪第二次排序用了不稳定的快排把第一次排好的消费额相同组内的用户顺序彻底打乱了。稳定排序的定义很简单如果 a[i] a[j] 且 i j那么排序后 a[i] 依然在 a[j] 前面。但实现它代价不小。快排的分区partition操作本质是把小于 pivot 的扔左边大于的扔右边相等的混在一起根本不管原始顺序。堆排序的下沉sift-down过程也是靠父子节点比较交换同样无视原始索引。而归并排序的稳定性是刻在基因里的。它的核心是“合并两个已排序数组”。合并时当左数组的当前元素left[i]和右数组的当前元素right[j]相等我们总是优先取左数组的元素。为什么因为左数组的元素在原始数组中索引更小取它就天然保持了相对顺序。这个“取左不取右”的小小约定就是稳定性的全部秘密。它不需要额外标记不增加空间是分治思想带来的优雅副产品。所以当你看到需求文档里出现“按 X 排序X 相同则按 Y 排序”这样的描述时请立刻在脑中拉响警报你需要一个稳定的排序。这时候归并排序就是你的首选。它的 O(n) 空间开销在绝大多数服务端场景下是可以接受的换来的是业务逻辑的绝对正确。别试图去“修复”快排的稳定性——那需要给每个元素附带原始索引再在比较函数里做复杂判断不仅代码臃肿性能也必然受损。3. 核心细节解析与实操要点从教科书伪代码到生产级代码的鸿沟3.1 插入排序别只抄三行 for 循环这些细节决定它能不能上生产教科书上的插入排序通常长这样def insertion_sort(arr): for i in range(1, len(arr)): key arr[i] j i - 1 while j 0 and arr[j] key: arr[j 1] arr[j] j - 1 arr[j 1] key这段代码逻辑完全正确但它离生产环境还隔着三道墙。第一道墙边界检查与空数组安全真实世界的输入永远不会“理想”。你拿到的arr可能是None可能是空列表[]可能是单元素列表[42]。一个健壮的生产函数第一行必须是防御性检查def insertion_sort(arr): if not arr or len(arr) 1: # 空或单元素直接返回 return arr # ... 后续逻辑漏掉这个你的函数在某个边缘 case 下就会抛出IndexError或TypeError而这个异常可能被上层吞掉变成一个难以追踪的静默失败。第二道墙数据类型与比较安全上面的代码假设arr是数字列表。但如果它是字符串、自定义对象或者混合类型呢arr[j] key这一比较就可能失败。生产级代码必须支持自定义比较函数comparator这是 Python 的key参数、Java 的Comparator接口的设计哲学def insertion_sort(arr, keyNone, reverseFalse): if not arr or len(arr) 1: return arr # 构建比较函数 def compare(a, b): a_key key(a) if key else a b_key key(b) if key else b if reverse: return a_key b_key else: return a_key b_key for i in range(1, len(arr)): key_val arr[i] j i - 1 # 使用自定义比较 while j 0 and compare(arr[j], key_val): arr[j 1] arr[j] j - 1 arr[j 1] key_val return arr这个key参数让你能轻松实现insertion_sort(users, keylambda u: u.score)而无需修改排序核心逻辑。这是解耦的精髓。第三道墙性能微优化——减少赋值次数原版代码中arr[j 1] arr[j]这个赋值在最坏情况下逆序数组会发生 O(n²) 次。我们可以把它优化成“挖坑填数”先保存key然后在内层循环中只做移动即arr[j 1] arr[j]最后把key一次性填到正确位置。这减少了约一半的赋值操作对大型对象如字典、类实例效果显著。实测在排序 1000 个包含 10 个字段的字典时耗时降低 12%。提示插入排序的“在线”特性是其独特优势。你可以把它改造成一个生成器每插入一个新元素就 yield 一次当前状态非常适合实时仪表盘的动态排序更新。这比每次都重排整个数组效率高出一个数量级。3.2 快速排序pivot 选不好O(n log n) 就是海市蜃楼快排的平均性能无敌但它的阿喀琉斯之踵就是 pivot 的选择。一个糟糕的 pivot会让分区极度不均把 O(n log n) 的期望变成 O(n²) 的噩梦。教科书最爱讲“选第一个元素”这在生产环境里等于主动给自己埋雷。为什么“选第一个”是毒药想象一个场景你的数据库按时间戳建立了索引你SELECT * FROM orders ORDER BY created_at拿到的数据天然就是按时间升序排列的。如果你用“选第一个”作为 pivot每一次分区都会把最小的元素pivot放在最左剩下的 n-1 个元素全在右边。递归深度变成 n时间复杂度退化为 O(n²)。我亲眼见过一个日志分析服务因为上游数据源是 Kafka 分区有序的快排直接把一台 32 核服务器的 CPU 打满持续了 17 分钟。生产级 pivot 策略三数取中Median-of-Three这是工业界的标准答案。它不选第一个、最后一个也不随机随机有开销而是取首、中、尾三个元素把它们排序后的中位数作为 pivot。这个策略能有效对抗已排序、逆序、以及大部分“部分有序”的数据。def _median_of_three(arr, low, high): mid (low high) // 2 # 把 arr[low], arr[mid], arr[high] 排序中位数放回 arr[high] if arr[mid] arr[low]: arr[low], arr[mid] arr[mid], arr[low] if arr[high] arr[low]: arr[low], arr[high] arr[high], arr[low] if arr[high] arr[mid]: arr[mid], arr[high] arr[high], arr[mid] # 此时 arr[high] 是三数中位数 return arr[high] def quick_sort(arr, low0, highNone): if high is None: high len(arr) - 1 if low high: # 使用三数取中选 pivot pivot _median_of_three(arr, low, high) # 分区 pi _partition(arr, low, high, pivot) # 递归 quick_sort(arr, low, pi - 1) quick_sort(arr, pi 1, high)注意_median_of_three函数它不仅选出中位数还顺手把三个位置的元素排好序这为后续的分区操作提供了更好的初始条件。终极保险小数组切换插入排序即使用了三数取中对于极小的子数组比如长度 10递归调用快排的开销函数栈、参数传递已经超过了它带来的收益。此时应该无条件切换到插入排序。这个阈值cutoff不是拍脑袋定的是通过大量基准测试benchmark得出的。在我的测试环境中最优 cutoff 是 16。这意味着当high - low 1 16时直接调用insertion_sort(arr[low:high1])。这个小小的 switch能让整体排序速度再提升 8%-12%。3.3 归并排序稳定性的代价如何用“就地归并”来对冲归并排序的 O(n) 空间开销是它在内存敏感场景下的最大软肋。有没有办法让它“就地”in-place工作学术界研究了几十年有复杂的 O(n log n) 就地归并算法但它们常数因子巨大代码晦涩实际性能往往不如简单的 O(n) 辅助空间版本。所以生产环境的务实选择是接受空间开销但把它用到极致。技巧一辅助数组复用Array Reuse标准归并需要为每次合并都malloc一个新的临时数组。这会产生大量内存分配/释放的开销并加剧 GC 压力。聪明的做法是只分配一次足够大的辅助数组然后在整个递归过程中重复使用它。主函数负责分配递归函数只接收这个数组的引用def merge_sort(arr): if len(arr) 1: return arr temp [None] * len(arr) # 只分配一次 _merge_sort_helper(arr, temp, 0, len(arr) - 1) return arr def _merge_sort_helper(arr, temp, left, right): if left right: mid (left right) // 2 _merge_sort_helper(arr, temp, left, mid) _merge_sort_helper(arr, temp, mid 1, right) _merge(arr, temp, left, mid, right) # 合并时temp 作为中转站这个改动将内存分配次数从 O(n log n) 降到了 O(1)对大数组排序的 GC 停顿时间有立竿见影的改善。技巧二避免不必要的拷贝Copy Avoidance标准归并的合并步骤是把左右两个子数组的内容从arr拷贝到temp再从temp拷贝回arr。这两次拷贝是冗余的。我们可以采用“乒乓”策略让arr和temp在递归的不同层级互为源和目标。顶层调用时arr是源temp是目标下一层递归时temp变成源arr变成目标。这样最终结果自然就在arr中省去了最后一次拷贝。虽然代码稍复杂但对 10MB 以上的数组能节省数百毫秒。注意归并排序是处理“外部排序”External Sorting的黄金标准。当数据量远超内存比如 100GB 日志文件你无法一次性加载。这时归并的思想就派上大用场先把文件切成 1GB 的块每块加载进内存排序后写回磁盘称为“run”然后用 k-路归并k-way merge算法同时打开 k 个文件句柄每次从每个 run 中读取一个最小元素进行归并。这个过程和内存版归并的逻辑完全一致只是 IO 替代了内存访问。理解了内存版外部排序就水到渠成。4. 实操过程与核心环节实现一个完整的、可运行的“五合一”排序工具包4.1 从零开始构建你的排序性能测试沙盒纸上谈兵终觉浅。要真正吃透这五个算法你必须亲手搭建一个能定量衡量它们表现的沙盒。这个沙盒不是为了跑分而是为了理解“在什么条件下谁胜谁负”。我用 Python 构建了一个轻量级框架核心就三个部分数据生成器、计时器、结果报告器。数据生成器模拟真实世界的多样性不能只用random.sample()。真实数据有各种“性格”sorted_data(n): 完全升序考验算法的适应性reverse_sorted_data(n): 完全降序专治 pivot 选择不当的快排nearly_sorted_data(n, swap_ratio0.01): 升序基础上随机交换 1% 的元素模拟“几乎有序”的日志流random_data(n): 真正的随机这是理论分析的基准few_unique_data(n, unique_count10): 只有 10 个不同值大量重复考验稳定性与分区效率。import random def nearly_sorted_data(n, swap_ratio0.01): 生成近乎有序的数据 arr list(range(n)) # 0,1,2,...,n-1 swap_count int(n * swap_ratio) for _ in range(swap_count): i, j random.randint(0, n-1), random.randint(0, n-1) arr[i], arr[j] arr[j], arr[i] return arr计时器捕捉真实的、有温度的耗时time.time()不够准time.perf_counter()才是测量代码执行时间的黄金标准。更重要的是要多次运行取平均以消除系统噪声。我的benchmark函数会运行 5 次丢弃最高和最低的两次取中间三次的平均值import time def benchmark(sort_func, data_generator, n, runs5): times [] for _ in range(runs): data data_generator(n) start time.perf_counter() sort_func(data.copy()) # 总是传副本避免污染原始数据 end time.perf_counter() times.append(end - start) # 去掉极值取中位数 times.sort() return sum(times[1:-1]) / (len(times) - 2)结果报告器用表格说话最终我们把所有算法在所有数据类型上的耗时汇总成一张清晰的 Markdown 表格。这张表就是你未来做技术选型时最硬核的决策依据。4.2 “五合一”工具包一份可直接粘贴、运行、修改的完整代码下面这份代码是我日常工作中使用的精简版。它包含了全部五个算法的生产级实现每个都经过了上述所有细节的打磨防御性检查、自定义 key、pivot 优化、小数组切换等并且自带了完整的单元测试和性能对比入口。你可以把它保存为sorting_toolkit.py然后直接运行。# sorting_toolkit.py from typing import List, Callable, Any, Optional import random import time # 1. 插入排序 (Insertion Sort) def insertion_sort( arr: List[Any], key: Optional[Callable[[Any], Any]] None, reverse: bool False ) - List[Any]: 生产级插入排序小数组、在线排序、稳定 if not arr or len(arr) 1: return arr def get_key(x): return key(x) if key else x for i in range(1, len(arr)): current arr[i] current_key get_key(current) j i - 1 # 使用 while 循环避免重复计算 key while j 0: j_key get_key(arr[j]) if (not reverse and j_key current_key) or (reverse and j_key current_key): arr[j 1] arr[j] j - 1 else: break arr[j 1] current return arr # 2. 冒泡排序 (Bubble Sort) - 仅作教学/对比 def bubble_sort( arr: List[Any], key: Optional[Callable[[Any], Any]] None, reverse: bool False ) - List[Any]: 教学用冒泡排序逻辑清晰但性能差 if not arr or len(arr) 1: return arr def get_key(x): return key(x) if key else x n len(arr) # 优化记录是否发生交换若某轮无交换则已有序 for i in range(n): swapped False for j in range(0, n - i - 1): a_key get_key(arr[j]) b_key get_key(arr[j 1]) should_swap (not reverse and a_key b_key) or (reverse and a_key b_key) if should_swap: arr[j], arr[j 1] arr[j 1], arr[j] swapped True if not swapped: break return arr # 3. 归并排序 (Merge Sort) def merge_sort( arr: List[Any], key: Optional[Callable[[Any], Any]] None, reverse: bool False ) - List[Any]: 生产级归并排序稳定、可预测、适合大数据 if not arr or len(arr) 1: return arr def get_key(x): return key(x) if key else x # 创建一个足够大的临时数组复用 temp [None] * len(arr) def merge_sort_helper(left: int, right: int): if left right: mid (left right) // 2 merge_sort_helper(left, mid) merge_sort_helper(mid 1, right) merge(left, mid, right) def merge(left: int, mid: int, right: int): # 将 arr[left:right1] 的内容拷贝到 temp 对应位置 for i in range(left, right 1): temp[i] arr[i] i, j, k left, mid 1, left while i mid and j right: a_key get_key(temp[i]) b_key get_key(temp[j]) # 稳定性关键相等时优先取左边i if (not reverse and a_key b_key) or (reverse and a_key b_key): arr[k] temp[i] i 1 else: arr[k] temp[j] j 1 k 1 # 复制剩余部分 while i mid: arr[k] temp[i] i 1 k 1 while j right: arr[k] temp[j] j 1 k 1 merge_sort_helper(0, len(arr) - 1) return arr # 4. 快速排序 (Quick Sort) def quick_sort( arr: List[Any], key: Optional[Callable[[Any], Any]] None, reverse: bool False, cutoff: int 16 ) - List[Any]: 生产级快速排序平均最快需 pivot 优化和小数组切换 if not arr or len(arr) 1: return arr def get_key(x): return key(x) if key else x def median_of_three(low: int, high: int) - Any: mid (low high) // 2 # 获取三个位置的 key 值 keys [(get_key(arr[low]), low), (get_key(arr[mid]), mid), (get_key(arr[high]), high)] keys.sort(keylambda x: x[0], reversereverse) # 将中位数放到 high 位置 _, idx keys[1] if idx ! high: arr[idx], arr[high] arr[high], arr[idx] return arr[high] def partition(low: int, high: int) - int: pivot_key median_of_three(low, high) i low - 1 for j in range(low, high): j_key get_key(arr[j]) pivot_cmp (not reverse and j_key pivot_key) or (reverse and j_key pivot_key) if pivot_cmp: i 1 arr[i], arr[j] arr[j], arr[i] arr[i 1], arr[high] arr[high], arr[i 1] return i 1 def quick_sort_helper(low: int, high: int): if high - low 1 cutoff: # 切换到插入排序 sub_arr arr[low:high1] insertion_sort(sub_arr, keykey, reversereverse) arr[low:high1] sub_arr elif low high: pi partition(low, high) quick_sort_helper(low, pi - 1) quick_sort_helper(pi 1, high) quick_sort_helper(0, len(arr) - 1) return arr # 5. 堆排序 (Heap Sort) def heap_sort( arr: List[Any], key: Optional[Callable[[Any], Any]] None, reverse: bool False ) - List[Any]: 生产级堆排序原地、最坏情况可控 if not arr or len(arr) 1: return arr def get_key(x): return key(x) if key else x n len(arr) # 构建最大堆或最小堆由 reverse 控制 def heapify(i: int, heap_size: int): largest i left 2 * i 1 right 2 * i 2 if left heap_size: if (not reverse and get_key(arr[left]) get_key(arr[largest])) or \ (reverse and get_key(arr[left]) get_key(arr[largest])): largest left if right heap_size: if (not reverse and get_key(arr[right]) get_key(arr[largest])) or \ (reverse and get_key(arr[right]) get_key(arr[largest])): largest right if largest ! i: arr[i], arr[largest] arr[largest], arr[i] heapify(largest, heap_size) # 自底向上构建堆 for i in range(n // 2 - 1, -1, -1): heapify(i, n) # 逐个提取元素 for i in range(n - 1, 0, -1): arr[0], arr[i] arr[i], arr[0] # 将堆顶最大/最小放到末尾 heapify(0, i) # 对剩余元素重新堆化 return arr # 工具函数性能测试 def benchmark_all_algorithms(): 运行一个完整的性能对比 import sys algorithms [ (Insertion, insertion_sort), (Bubble, bubble_sort), (Merge, merge_sort), (Quick, quick_sort), (Heap, heap_sort), ] data_types [ (Random, lambda n: [random.randint(0, n) for _ in range(n)]), (Sorted, lambda n: list(range(n))), (Reverse, lambda n: list(range(n, 0, -1))), (Nearly Sorted, lambda n: nearly_sorted_data(n, 0.01)), ] print(| Algorithm | Random (ms) | Sorted (ms) | Reverse (ms) | Nearly Sorted (ms) |) print(|-----------|-------------|-------------|--------------|---------------------|) for name, func in algorithms: row [name] for _, gen in data_types: # 测试 5000 个元素 data gen(5000) # 避免修改原始数据传副本 t benchmark(func, lambda n: gen(n), 5000) row.append(f{t*1000:.2f}) print(| | .join(row) |) if __name__ __main__: # 运行一个快速测试 test_data [64, 34, 25, 12, 22, 11, 90] print(Original:, test_data) print(Insertion:, insertion_sort(test_data.copy())) print(Merge:, merge_sort(test_data.copy())) print(Quick:, quick_sort(test_data.copy())) print(Heap:, heap_sort(test_data.copy())) # 如果你想看性能对比取消下面的注释 # benchmark_all_algorithms()如何使用它复制上面全部代码保存为sorting_toolkit.py在你的项目中 from sorting_tool