快速排序的实现
手写实现快速排序算法,掌握分治思想和递归技巧
问题
快速排序(Quick Sort)是一种高效的排序算法,采用分治法策略。需要实现一个快速排序函数,能够对数组进行原地排序或返回新的排序数组。快速排序的思想是:选择一个基准元素,将数组分为两部分,左边都小于基准,右边都大于基准,然后递归地对两部分进行排序。
解答
/**
* 快速排序 - 原地排序版本
* @param {number[]} arr - 待排序数组
* @param {number} left - 左边界
* @param {number} right - 右边界
* @return {number[]} 排序后的数组
*/
function quickSort(arr, left = 0, right = arr.length - 1) {
// 递归终止条件
if (left >= right) {
return arr;
}
// 获取分区点位置
const pivotIndex = partition(arr, left, right);
// 递归排序左右两部分
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
return arr;
}
/**
* 分区函数 - 将数组分为小于和大于基准值的两部分
* @param {number[]} arr - 数组
* @param {number} left - 左边界
* @param {number} right - 右边界
* @return {number} 基准值最终位置
*/
function partition(arr, left, right) {
// 选择最右边的元素作为基准值
const pivot = arr[right];
// i 指向小于基准值区域的最后一个元素
let i = left - 1;
// 遍历数组,将小于基准值的元素移到左边
for (let j = left; j < right; j++) {
if (arr[j] < pivot) {
i++;
// 交换元素
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
// 将基准值放到正确的位置
[arr[i + 1], arr[right]] = [arr[right], arr[i + 1]];
return i + 1;
}
/**
* 快速排序 - 非原地排序版本(更易理解)
* @param {number[]} arr - 待排序数组
* @return {number[]} 新的排序后数组
*/
function quickSortSimple(arr) {
// 递归终止条件
if (arr.length <= 1) {
return arr;
}
// 选择中间元素作为基准值
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr[pivotIndex];
// 分成三部分:小于、等于、大于基准值
const left = [];
const middle = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else if (arr[i] === pivot) {
middle.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 递归排序并合并
return [...quickSortSimple(left), ...middle, ...quickSortSimple(right)];
}
使用示例
// 示例1:基本使用
const arr1 = [64, 34, 25, 12, 22, 11, 90];
console.log('原数组:', arr1);
quickSort(arr1);
console.log('排序后:', arr1);
// 输出: [11, 12, 22, 25, 34, 64, 90]
// 示例2:使用非原地排序版本
const arr2 = [5, 2, 9, 1, 7, 6, 3];
const sorted = quickSortSimple(arr2);
console.log('原数组:', arr2); // [5, 2, 9, 1, 7, 6, 3] 不变
console.log('排序后:', sorted); // [1, 2, 3, 5, 6, 7, 9]
// 示例3:包含重复元素
const arr3 = [3, 1, 4, 1, 5, 9, 2, 6, 5];
quickSort(arr3);
console.log('排序后:', arr3);
// 输出: [1, 1, 2, 3, 4, 5, 5, 6, 9]
// 示例4:已排序数组
const arr4 = [1, 2, 3, 4, 5];
quickSort(arr4);
console.log('排序后:', arr4);
// 输出: [1, 2, 3, 4, 5]
// 示例5:倒序数组
const arr5 = [5, 4, 3, 2, 1];
quickSort(arr5);
console.log('排序后:', arr5);
// 输出: [1, 2, 3, 4, 5]
关键点
-
分治思想:将大问题分解为小问题,递归解决后合并结果,这是快速排序的思想
-
基准值选择:可以选择第一个、最后一个、中间或随机元素作为基准值,不同选择影响算法性能
-
分区操作:通过一次遍历将数组分为两部分,左边小于基准值,右边大于基准值,这是算法的关键步骤
-
原地排序:通过交换元素实现原地排序,空间复杂度为 O(log n)(递归栈空间)
-
时间复杂度:
- 平均情况:O(n log n)
- 最坏情况:O(n²)(数组已排序或逆序时)
- 最好情况:O(n log n)
-
不稳定排序:相同元素的相对位置可能会改变
-
优化技巧:
- 三数取中法选择基准值
- 小数组使用插入排序
- 尾递归优化减少栈空间
目录