快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),
简称快排,一种排序算法,最早由东尼·霍尔提出。
在平均状况下,排序n个项目要O(log n)(大O符号)次比较。
在最坏状况下则需要 O(n^2)次比较,但这种状况并不常见。
事实上,快速排序通常明显比其他算法更快,
因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成。
解释
以上是 维基百科 关于快速排序的描述,转换为人话就是大概这么几条:
-
分治策略
把原来为n
的问题拆分
为几个部分 - 确定一个
中心点
,通过以中心点为基准
去做判断 - 平均执行时间都是
O(lgn)
首先快速通过确定一个中心点,让其余元素依次去和该中心点去比较。如果比中心点小则放左边,比中心点大则放右边。这个放左右边并不是简简单单的判断大小而已,比如现在有个数组:
const arr = [1,8,3,9,4,5,7]
现在要对它进行快速排序
,核心
在于判断中心点
。大概有这么几个流程:
- 确定一个
中心点
(默认先以最后一个元素7作为中心点)- 拿这个中心点
依次
去和数组中的其他元素做比较
- 如果数组中某个元素
小于
该中心点,说明顺序正确,不做处理
- 如果数组中某个元素
大于
该中心点,需要做交换
- 从
左边
数第一个大于
中心点的值,要和从右边
数第一个小于
中心点的值做交换
,那么此时做完交换,原数组就变成了:
const arr = [1,5,3,9,4,8,7]
6.从左边
数第二个大于
中心点的值,要和从右边
数第二个小于
中心点的值做交换
,那么此时做完交换。此时,原数组又变成了:
const arr = [1,5,3,4,9,8,7]
7.最后,很重要一点。要将中心点
放到数组的中间位置
:
const arr = [1,5,3,4,7,9,8]
8.那么可以看到,第一个中心点7
,便被确定好
了位置。7永远只能
在这个
位置,不可能变换位置。接下来对中心点左右两边
的剩余数组,做相同的递归处理
即可。
上图是一个快速排序的动态演示。这个中心点是可以随意选取的,上面动图中是以起点作为中心点。
JS实现
/**
* 快速排序
* @param {无序数组} arr
* @param {起点} left
* @param {终点} right
*/
function qsort(arr, left = 0, right = arr.length) {
if (right - left <= 1) return false; // 数组长度小于1 返回
// 首先通过 partition 函数来确定中心点位置
const m = partition(arr, left, right);
// 递归中心点左边数组区间
qsort(arr, left, m);
// 递归中心点右边数组区间
qsort(arr, m + 1, right);
// 该函数很简单 就是一个递归函数,核心在下方的 partition 函数
}
/**
* 该函数完成的就是上述的8条流程
* @param {无序数组} arr
* @param {起点} left
* @param {终点} right
*/
function partition(arr, left, right) {
let middle = arr[right - 1]; // 取数组中最后一个值作为中心点
let i = left;
let j = right - 1; // 最后索引
while (i !== j) {
// 当i等于j的时候,就表示所有元素都已确认 (i 不断增加 j不断减小)
if (arr[i] < middle) {
// 元素小于中心点 不做处理
i++; // i + 1 判断下一个元素
} else {
// 元素大于中心点 做值交换 同时j要--
swap(arr, i, --j);
}
// i 不断增加 j不断减小 那么:
// 此时 未排序确认的范围就是[i, j]
// 小于中心点范围: [left, i) 左闭右开区间
// 大于中心点范围: [j, right - 1) 左闭右开区间
}
// while循环结束之后 要再做一次最后的值交换
// 调整中心点位置 将位于数组末尾的中心点 放到属于它的位置
// 即是i的位置(或者j, 因为while结束后i===j)
swap(arr, i, right - 1);
// 最后返回中心点位置
return i;
}
function swap(arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
测试一下
const arr_1 = [10, 50, 30, 90, 40, 80, 70];
qsort(arr_1);
console.log(arr_1);
//[10, 30, 40, 50,70, 80, 90]
其实说白了,快速排序就是一个不断确定中心点
,不断分割数组
的过程。这个分割数组并不是真正的在电脑内存去新建空间。而是通过中心点将左右两边
标识为两个区间
。再对这两个区间做递归操作
。所以,快速的排序的空间复杂度
很小,仅为O(1)
。
尾递归优化 qsort
/**
* 快速排序
* @param {无序数组} arr
* @param {起点} left
* @param {终点} right
*/
function qsort(arr, left = 0, right = arr.length) {
while(left < right){ // 增加while判断条件
// 和上面一样
const m = partition(arr, left, right);
// 首先递归中心点左边数组区间 和上面一样
qsort(arr, left, m);
// 上面左区间递归完毕之后 将left赋值
// 等于p+1 也就是右区间的第一个值
// 这样就开启了右区间的比较
// 通过一个while 去掉一个函数调用 性能得到提升
left = m + 1;
}
}