排序算法稳定性比较:哪些算法会打乱相等元素的顺序?

你有没有遇到过这种情况:给学生成绩排序,相同分数的学生原本按名字字母顺序排好,结果一排序,顺序全乱了?问题可能就出在排序算法的“稳定性”上。

什么是排序的“稳定性”?

简单说,如果两个相等的元素在排序前后的相对位置不变,那这个排序算法就是稳定的。比如数组里有两个 80 分,前面那个始终在前面,这就是稳定;如果它们的位置可能互换,那就是不稳定。

常见的排序算法,哪些稳,哪些不稳?

拿我们平时写代码常用的几种算法来说:

  • 冒泡排序——稳定。它只在相邻元素逆序时才交换,相等时不动作,所以不会打乱原有顺序。
  • 插入排序——稳定。新元素插入时,只会插到比它大的元素前面,等于它的不动,保持了原序。
  • 归并排序——稳定。合并两个有序子数组时,如果遇到相等元素,优先取左边的,所以不会乱。
  • 快速排序——不稳定。它的核心是分区操作,相等元素可能被分到不同侧,最终顺序无法保证。
  • 堆排序——不稳定。建堆过程会大幅调整元素位置,相等元素谁前谁后完全不可控。
  • 选择排序——也不稳定。虽然看起来简单,但它每次找最小值和当前位置交换,可能把后面相等的元素提前。

实际场景中的影响

举个例子:你导出了一份订单数据,先按下单时间排好,现在想按金额排序。如果用的是不稳定的算法,相同金额的订单可能会把时间顺序打乱,导致分析出错。

再比如学生管理系统,先按班级排,再按成绩排。如果排序不稳定,同分学生之间的班级内顺序可能就没了。

代码示例:稳定与不稳定的体现

假设我们有一个表示(分数, 姓名首字母)的数组,想按分数升序排:

[ (80, 'A'), (75, 'B'), (80, 'C') ]

理想情况下,排序后应该是:

[ (75, 'B'), (80, 'A'), (80, 'C') ]

这里 80 分的 A 还在 C 前面,顺序没变。但如果是不稳定的快排,有可能变成:

[ (75, 'B'), (80, 'C'), (80, 'A') ]

虽然分数是对的,但学生顺序乱了,后续处理可能出问题。

怎么选?看需求

如果你只是排一串数字,不在乎相等值的前后关系,那快排、堆排序这些效率高的完全可以放心用。但一旦涉及多级排序、或需要保留原始相对顺序,就得挑稳定的算法,比如归并排序。

有些语言内置的排序函数默认是稳定的。比如 Python 的 sorted()list.sort() 都基于 Timsort,是稳定的。而 C++ 的 std::sort() 不保证稳定,要用 stable_sort() 才行。

了解这一点,能帮你避开一些隐蔽的 bug。下次写排序逻辑时,不妨多问一句:我需要稳定吗?