快速排序曾被部分 v8 作为 Array.prototype.sort 的实现,快排是不稳定的排序,其平均复杂度为 O(NlogN) 若选定左侧作为基准值,升序排序的情况下,有以下实现,参看视频:
排序思路
1、选定基准值,将比基准值小的放在其左侧,比基准值大的放在右侧: 右哨兵从右往左扫描找到比基准值小,左哨兵从左往右扫描找到比基准值大的,两者互换。
2、持续扫描直至二者相遇,将相遇处值与基准值互换。
3、分治,左侧右侧两个分区继续进行以上逻辑(通过递归实现),直到所有分区排序完毕。
i 为相遇点,left 为左边界,right 为右边界
quick_sort(arr, left, i - 1);
quick_sort(arr, i + 1, right)
排序要点
- 分治,递归
- 注意边界条件,i<j
- 注意需要从右到左先遍历
排序过程
给定原数组,排序主要过程如下:
const arr=[30,40,60,10,20,50];
/**
* 基准值交换
* [30,40,60,10,20,50];
* 第一轮交换
* 30,40,60,10,20,50
* ^ ^
* 第一轮交换结束
* 30,20,60,10,40,50
* ^ ^
* 第二轮交换
* 30,20,60,10,40,50
* ^ ^
* 第二轮交换结束
* 30,20,10,60,40,50
* ^ ^
* 第三轮交换,左右哨兵相遇,置换相遇位置与基准值
* 30,20,10,60,40,50
* ^
* ^
* 第三轮交换结束
* 10,20,30,60,40,50
* ^ ^
*
* 基准值换
* 10,20
* 第一轮交换
* 10,20
* ^ ^
* 左右哨兵相遇,置换相遇位置与基准值
* 10,20
* ^
* ^
* 第一轮交换结束
* 10,20
*
* 基准值交换
* 0 1=>
* 0 -1(left>right直接返回)
* 1 1
*
* 1 1=>
* 1 0(left>right直接返回)
* 2 1(left>right直接返回)
*/
排序实现
const quick_sort = (arr, left, right) => {
if (!arr.length || left > right) {
console.log(`return left-${left}|right-${right}`);
return;
}
console.log(`left-${left}|right-${right}`);
console.log(`baseArr|${JSON.stringify(arr)}`);
let base = arr[left];//基准值
console.log(`base-${base}|baseIndex-${left}`);
let i = left;
let j = right;
while (i < j) {
console.log(`arr[i]-${arr[i]}|arr[j]-${arr[j]}`);
// 因为是需要升序,基准值右侧需要比基准值大(包括等于),从右往左找比基准值小的,找到后暂停,等待与从左往右找到比基准值大的值交换
while (arr[j] >= base && i < j) {
j--;
}
// 找到从左往右找到比基准值大的值,当 arr[i] 比基准值小(包括等于),则一直循环
while (arr[i] <= base && i < j) {
i++;
}
console.log(`i-${i}|j-${j}`);
if (i < j) {
// 交换左右,es6 解构赋值
[arr[i], arr[j]] = [arr[j], arr[i]];
// let temp=arr[i];
// arr[i]=arr[j];
// arr[j]=temp;
}
console.log(`inside ${JSON.stringify(arr)}`);
}
console.log(`i-${i}`);
// 左右哨兵相遇,此时相遇位置值与基准值互换
arr[left] = arr[i];
arr[i] = base;
console.log(`outside ${JSON.stringify(arr)}`);
quick_sort(arr, left, i - 1);
quick_sort(arr, i + 1, right)
}
quick_sort(arr, 0, arr.length - 1);
console.log(arr);
先从右往左扫描的理由
/**
* 至于为什么需要先从右往左遍历,主要是先后顺序会影响升序降序的顺序,考虑这样的一个例子
* 原数组:[3,7,4,5,8,6,2]
* 设置基准值为左侧第一位3,
*
* 若从左往右遍历,
* 第一轮交换
* 先寻找比基准值大的,找到7;后从右往左,找到比基准值小的2,
* [3,7,4,5,8,6,2]
* ^ ^
* 第一轮交换后数组
* [3,2,4,5,8,6,7]
* ^ ^
* 第二轮交换
* 先寻找比基准值大的,找到4;后从右往左,遇到4,停止寻找(即需满足i<j)
* [3,2,4,5,8,6,7]
* ^
* ^
* 此时进行基准值与边界值的交换
* 第二轮交换后数组
* [4,2,3,5,8,6,7]
* ^ ^
* 此时可以看到,基准值左侧的部分数组降序了,不符合整体升序要求
*
*
* 原数组:[3,7,4,5,8,6,2]
* 若从右往左遍历,
* 第一轮交换
* 先寻找比基准值小的,找到2;后从左往右,找到比基准值大的7,
* [3,7,4,5,8,6,2]
* ^ ^
* 第一轮交换后数组
* [3,2,4,5,8,6,7]
* ^ ^
* 第二轮交换
* 先寻找比基准值小的,找到2;后从左往右,遇到2,停止寻找(即需满足i<j)
* [3,2,4,5,8,6,7]
* ^
* ^
* 此时进行基准值与边界值的交换
* 第二轮交换后数组
* [2,3,4,5,8,6,7]
* ^ ^
* 此时可以看到,基准值左侧的部分数组是升序,符合整体升序要求
*/
参考文档
| 标题 | 链接 |
|---|---|
| 仓库地址 | gitee.com/zhu_zhi_kan… |
| 讲解录屏 | www.bilibili.com/video/BV1SA… |
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情