一、快速排序算法(Quick Sort)简介
快速排序(Quick sort),使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。它是一种 分而治之思想 在排序算法上的典型应用。本质上来看,快速排序是对冒泡排序算法的一种改进,是在冒泡排序基础上的 递归分治法。
快速排序算法的优点在于 快,而且效率高 !它是处理大数据最快的排序算法之一了。
二、 算法基本思想和流程(期望时间复杂度 ,最坏情况
- Step1——确定分界点
x
(三种常用的方法):q[l]
、q[(l+r)/2]
、q[r]
,或者随机确定。 - Step2——将待排序列调整成两个区间,使得满足以下两个原则(具体方法后面介绍):
- 原则一:第一个区间里的所有数都
≤x
; - 原则二:第二个区间里的所有数都
≥x
;
注意:要注意此处写代码的逻辑。并不是把所有小于等于x的数放在左边,所有大于等于x的数放在右边。 因为等于x的数可能在左边也可能在右边。
- Step3——递归处理左、右两端,将左边和右边都排好序,Finish!
2. 难点/关键点:步骤二的区间调整方法
(1)暴力做法: 用两个额外的空间(增加了空间复杂度,但时间复杂度不变:O(n))
- 定义两个额外的数组
a[]
和b[]
,其中数组a[]
用来存放≤x
的所有数、数组b[]
用来存放>x
的所有数。
- 方法:遍历序列q,再写一个判断if-else分支语句即可。
- 然后,将
a
放前面,b
放后面组成的新数组q*
即实现了区间调整。
(2)优美做法: 无需开辟额外空间(时间复杂度:O(n))
- 定义两个指针:
i = l
以及j = r
,即指针的初始值一个在最左边,一个在最右边。 - 两指针分别往中间移动,并进行以下判断:
- 如果
q[i] < x
,则满足原则一,i++
,直到某个数q[i] ≥ x
,停下来,开始移动j
; - 如果
q[j] > x
,则满足原则二,j--
,直到某个数q[j] ≤ x
,停下来; - 交换两个指针指向的数:
swap(q[i], q[j])
。 - 返回第一步,继续移动
i
、j
,直到相遇为止。
三. 快排模板(背诵)
模板题:AcWing 785. 快速排序
注意:此题数据较大且多,输入输出建议使用
scanf
和printf
,要使用C++的cin
和cout
需要进行速度优化,参考另一篇博文:C++ 输入输出(cin & cout)加速/效率优化 。
模板一
void quick_sort(int q[], int l, int r)
{
// 1.判边界
if (l >= r) return;
// 2.区间调整:双指针法,指针边界往外扩了一格偏移量
// 原因是每次判断前无论是否满足两个原则,两指针都会提前往中间移动一格
// 注意:x 为下取整,递归时的边界是 j 和 j + 1.
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
//3.递归处理左、右两端
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
模板二
void quick_sort(int q[], int l, int r)
{
// 1.判边界
if (l >= r) return;
// 2.区间调整:双指针法,指针边界往外扩了一格偏移量
// 原因是每次判断前无论是否满足两个原则,两指针都会提前往中间移动一格
// 注意:x 为上取整,递归时的边界是 i - 1 和 i.
int i = l - 1, j = r + 1, x = q[l + r + 1 >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
//3.递归处理左、右两端
quick_sort(q, l, i - 1);
quick_sort(q, i, r);
}
【分析避免边界问题】
- 当递归边界取的
i
,即quick_sort(q, l, i - 1); quick_sort(q, i, r);
,则为了避免陷入死循环:
- 边界点
x
一定不能等于q[l]
,且如果取中间值,需要上取整x = q[l + r + 1 >> 1]
- 当递归边界取的
j
,即quick_sort(q, l, j); quick_sort(q, j + 1, r);
,则为了避免陷入死循环:
- 边界点
x
一定不能等于q[r]
,且如果取中间值,需要下取整x = q[l + r >> 1]