你在整理书架时,会不会先把书按作者分类,再按出版年份排列?如果第二次排序时,同一位作者的书突然乱了顺序,你肯定会觉得哪里出了问题。这其实就涉及到了排序算法中的一个关键特性——稳定性。
什么是排序算法的稳定性?
一个排序算法是稳定的,意味着相等元素在排序前后的相对位置不会改变。举个例子,有两本作者相同、但出版时间不同的书,原本较早的那本排在前面。稳定排序后,它依然应该在前面。
假设我们有一组数据,表示学生的姓名和班级:
{"name": "小明", "class": 2}
{"name": "小红", "class": 1}
{"name": "小刚", "class": 2}
{"name": "小丽", "class": 1}
如果我们按班级排序,而排序算法是稳定的,那么在每个班级内部,学生出现的顺序会保持原样。比如小红仍然在小丽前面,小明在小刚前面。
哪些常见算法是稳定的?
冒泡排序、插入排序、归并排序都是典型的稳定排序算法。它们在比较和移动元素时,不会打乱相等元素的原有次序。
以插入排序为例,它像整理扑克牌一样,每次把一张新牌插入到已排好序的部分中合适的位置。如果遇到相等的牌,通常选择插在后面,这样原来的顺序就被保留了。
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key; // 相等时不前移,保持原序
}
}
哪些又不稳定?
快速排序、堆排序通常不稳定。快排在分区过程中,元素的移动是跳跃式的,很难保证相等元素的相对位置不变。堆排序在构建堆和调整过程中也会打乱原有顺序。
比如你用快排对一组带优先级的任务排序,原本提交时间靠前的任务可能会被排到后面去,仅仅因为它们优先级相同但位置被交换了。
什么时候必须用稳定排序?
当你进行多关键字排序时,稳定性就变得至关重要。比如先按姓名排序,再按年龄排序。如果第二次排序破坏了第一次的结果,那最终名单就会混乱。
数据库系统中的 ORDER BY 子句常常依赖稳定排序来保证结果可预测。如果你查订单记录,按用户 ID 排完再按时间排,肯定希望同一个用户的订单还是按时间先后依次出现。
编程语言中的内置排序函数,比如 Python 的 sorted() 和 Java 的 Collections.sort(),都默认使用稳定算法(通常是归并排序或 Timsort),正是为了满足这类实际需求。
理解排序的稳定性,不只是为了应付面试题。它帮你选对工具,避免在处理复杂数据时掉进看似正确实则错乱的坑里。