快速排序(Quick Sort)和归并排序(Merge Sort)都是基于分治思想的高效排序算法,但它们在实现细节、性能特征和应用场景上有显著区别。以下是两者的主要区别:
核心思想
快速排序
- 通过选择一个基准元素(pivot),将数组分成两部分:左半部分 ≤ 基准 ≤ 右半部分。
- 递归地对左右子数组进行同样的操作,直到数组有序。
- 核心操作是分区(Partition),时间复杂度低但可能不均匀。
归并排序
- 将数组均等分成两半,递归排序每一半,再将两个有序子数组合并成一个有序数组。
- 核心操作是合并(Merge),需要额外的空间和时间进行合并。
时间复杂度
平均时间复杂度:两者均为 O(n log n)。
最坏时间复杂度:
- 快速排序:O(n²)(当分区极度不平衡时,如已有序数组且选到最差基准)。
- 归并排序:稳定为 O(n log n),不受输入数据影响。
空间复杂度
快速排序:原地排序(in-place),仅需递归栈空间,平均 O(log n)(递归深度);若实现为非递归版本,可优化为 O(1) 空间(但实际中很少使用)。
归并排序:需要额外空间合并子数组,空间复杂度为 O(n);若优化为链表实现或自底向上迭代版本,空间复杂度可降至 O(1)。
稳定性
快速排序不稳定,分区过程中可能打乱相同元素的原始顺序。
归并排序稳定(如果合并时对相等元素优先保留原顺序)。
性能关键点
快速排序:
- 性能依赖基准的选择。优化方法包括:三数取中、随机化基准等。
- 实际应用中常数因子更小,通常比归并排序更快。
归并排序:
- 性能依赖合并操作的效率,对内存访问模式友好(顺序访问)。
- 适合处理大数据或外部排序(如磁盘文件排序)。
应用场景
快速排序:适用于内存排序,尤其是对速度要求高、数据随机的场景;如 C 标准库的 qsort、Java 的 Arrays.sort()(对基本类型排序)。
归并排序:适合需要稳定性或外排序的场景(如大规模数据无法一次性加载到内存);如数据库排序、Python 的 sorted() 函数(稳定排序)。
code
// 合并两个已排序的数组
function merge(left, right) {
let result = [];
let leftIndex = 0, rightIndex = 0;
// 比较两个数组的元素,按顺序插入结果数组
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] <= right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}
// 将剩余元素拼接到结果数组
return result
.concat(left.slice(leftIndex)) // 左数组剩余部分
.concat(right.slice(rightIndex)); // 右数组剩余部分
}
// 归并排序主函数
function mergeSort(arr) {
// 递归终止条件:数组长度为 0 或 1 时直接返回
if (arr.length <= 1) {
return arr;
}
// 分割数组
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid); // 左半部分(不包含 mid)
const right = arr.slice(mid); // 右半部分(包含 mid)
// 递归排序并合并
return merge(
mergeSort(left), // 递归排序左半部分
mergeSort(right) // 递归排序右半部分
);
}
// 示例测试
console.log(mergeSort([3, 1, 4, 2])); // 输出: [1, 2, 3, 4]
console.log(mergeSort([5, 3, 5, 2, 7, 2])); // 输出: [2, 2, 3, 5, 5, 7]
console.log(mergeSort([-3, 0, 10, -2])); // 输出: [-3, -2, 0, 10]
console.log(mergeSort([])); // 输出: []
console.log(mergeSort([1])); // 输出: [1]
总结
特性 | 快速排序 | 归并排序 |
---|---|---|
分治策略 | 先分区后递归 | 先递归后合并 |
额外空间 | O(log n) (递归栈) |
O(n) (合并数组) |
稳定性 | 不稳定 | 稳定 |
最坏时间复杂度 | O(n²) |
O(n log n) |
适用场景 | 内存排序、随机数据 | 外排序、稳定性要求 |
- 选择快速排序:追求实际运行速度,内存有限,且不要求稳定性。
- 选择归并排序:需要稳定性、处理外排序,或接受额外空间开销。