日常开发中,你是不是还在纠结数组排序的效率问题?冒泡、选择、插入排序动辄O(n²)的时间复杂度,处理稍大数据量就卡顿;想上手快排又被「分治」「双指针」这些概念绕晕,看完理论还是写不出能跑的代码?
这篇文章,我会用最通俗的语言拆解快速排序核心逻辑,从实战场景出发,带你手写两种可直接运行的快排代码,避开新手常踩的坑,彻底搞懂为什么快排能做到平均O(nlogn)的高效排序!

一、先搞懂:快速排序到底快在哪?
在讲具体代码前,先明确快排的核心优势,这也是它能成为开发中最常用排序算法的原因:
- 分治策略:把一个大数组排序问题,拆成「基准值左边」「基准值右边」两个小数组的排序问题,递归解决,大幅缩小问题规模;
- 原地交换:无需额外开辟大量数组空间(仅递归栈占用O(logn)空间),双指针交换元素,减少内存开销;
- 时间效率:平均时间复杂度O(nlogn),远优于冒泡/选择/插入排序的O(n²),处理10万级数据也不慌。
快速排序核心思想(一句话讲透)
选一个「基准值(pivot)」,把数组分成两部分:比基准值小的放左边,比基准值大的放右边,然后递归对左右两部分重复这个操作,直到整个数组有序。
二、实战场景:给无序数组高效排序
假设我们有一个电商订单金额数组 [5, 3, 8, 1, 9, 2, 7, 4, 6],需要快速按金额从小到大排序,这就是快排最典型的实战场景。
下面给出两种常用的快排实现方式,可直接复制运行!
实现方式1:简洁版(易理解,新手首选)
这种方式用额外数组存储左右部分,代码逻辑直观,适合入门理解核心思想。
javascript
/**
* 快速排序(简洁版)
* 核心:选基准值,拆分数组,递归合并
* 时间复杂度:平均 O(nlogn),最坏 O(n²)
* 空间复杂度:O(n)(额外数组)
*/
function quickSort(arr) {
// 递归终止条件:数组长度≤1时直接返回
if (arr.length <= 1) return arr;
const pivot = arr[0]; // 取第一个元素作为基准值
const left = []; // 存比基准值小的元素
const right = []; // 存比基准值大的元素
// 遍历数组,拆分左右
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 递归排序左右数组,再合并结果
return [...quickSort(left), pivot, ...quickSort(right)];
}
// 测试代码(直接运行)
const arr = [5, 3, 8, 1, 9, 2, 7, 4, 6];
console.log('排序前:', arr);
console.log('排序后:', quickSort(arr)); // 输出:[1,2,3,4,5,6,7,8,9]
实现方式2:原地交换版(性能更优,生产常用)
简洁版需要额外数组,原地交换版通过双指针操作,无需额外空间,性能更优,是实际开发中更推荐的写法。
javascript
/**
* 快速排序(原地交换版)
* 核心:双指针+原地交换,减少内存开销
* 时间复杂度:平均 O(nlogn),最坏 O(n²)
* 空间复杂度:O(logn)(仅递归栈)
*/
// 分区函数:找到基准值最终位置,返回其索引
function partition(arr, left, right) {
const pivot = arr[left]; // 取左边界为基准值
let i = left, j = right; // 左右双指针
// 双指针未相遇时循环
while (i < j) {
// 右指针向左找:第一个比基准值小的元素
while (i < j && arr[j] >= pivot) {
j--;
}
// 找到后,把该元素放到左指针位置,左指针右移
if (i < j) {
arr[i] = arr[j];
i++;
}
// 左指针向右找:第一个比基准值大的元素
while (i < j && arr[i] <= pivot) {
i++;
}
// 找到后,把该元素放到右指针位置,右指针左移
if (i < j) {
arr[j] = arr[i];
j--;
}
}
// 基准值放到最终位置
arr[i] = pivot;
return i; // 返回基准值索引
}
// 递归排序函数
function quickSort(arr, left, right) {
// 递归终止条件:左右边界重合/交叉
if (left >= right) return;
// 分区,得到基准值索引
const pivotIndex = partition(arr, left, right);
// 递归排序左半部分
quickSort(arr, left, pivotIndex - 1);
// 递归排序右半部分
quickSort(arr, pivotIndex + 1, right);
}
// 测试代码(直接运行)
const arr2 = [5, 3, 8, 1, 9, 2, 7, 4, 6];
console.log('排序前:', arr2);
quickSort(arr2, 0, arr2.length - 1); // 注意:原地排序会修改原数组
console.log('排序后:', arr2); // 输出:[1,2,3,4,5,6,7,8,9]

三、踩坑提醒(新手必看)
- 基准值选择坑 :如果始终选数组第一个元素,当数组已经有序时,快排会退化成O(n²)! ✅ 解决方案:随机选基准值(比如
const pivot = arr[Math.floor(Math.random()*(right-left+1)) + left]),避免最坏情况。 - 相等元素处理坑 :快排是「不稳定排序」,相等元素的相对位置可能颠倒(比如数组
[2, 1, 2],排序后第二个2可能跑到第一个2前面)。 ✅ 解决方案:如果需要稳定排序,可改用归并排序;或在分区时把等于基准值的元素均匀分到左右两边。 - 递归栈溢出坑:处理超大数组时,递归深度过深可能导致栈溢出。 ✅ 解决方案:改用非递归实现(用栈模拟递归),或限制递归深度。
- 原地排序修改原数组 :原地交换版会直接修改原数组,若需保留原数组,排序前先复制(
const newArr = [...arr])。
四、核心知识点总结
- 快排核心:分治思想(分:拆分数组;治:递归排序;合:无需合并,原地排序)+ 基准值 + 双指针;
- 效率关键:基准值选得好(随机选),能让数组均匀拆分,时间复杂度接近O(nlogn);
- 版本选择 :
- 新手入门:先写简洁版,理解核心逻辑;
- 生产环境:用原地交换版,减少内存开销;
- 特性:平均效率高、原地排序,但不稳定,适合对性能要求高、无需稳定排序的场景(如大数据量数组排序)。
最后
快速排序是前端/后端开发中高频考察、高频使用的排序算法,吃透它不仅能解决实际开发中的排序问题,也能加深对分治、递归思想的理解。