你有没有遇到过这种情况:给学生成绩排序,相同分数的学生原本按名字字母顺序排好,结果一排序,顺序全乱了?问题可能就出在排序算法的“稳定性”上。
什么是排序的“稳定性”?
简单说,如果两个相等的元素在排序前后的相对位置不变,那这个排序算法就是稳定的。比如数组里有两个 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。下次写排序逻辑时,不妨多问一句:我需要稳定吗?