快速排序简介

快速排序是一种基于分治思想的排序算法,一般使用递归来实现,时间复杂度$O(nlogn)$

算法流程

我们首先设待排序序列为$l,r$

  1. 确定分界点:一般是$q[l]$(即左端点), $q[r]$(即右端点), $q[l + r >> 1]$,或随机一个点。注意,当分界点选取左右端点时,对于出题人的极端数据情况,可能会导致超时问题,或者因为边界问题造成死循环,因此,我们一般建议分界点选取为区间中点,$q[l + r >> 1]$。
  2. 调整区间:调整完区间后,使得区间以分界点为界,分界点左边都小于等于分界点,分界点右边都大于等于分界点,如下图所示(注意分解点不为中点,分界点对应的是区间中点下标位置对应的值) 划分区间.png
  3. 递归处理左右区间,当左右两边分别排好序,则整个区间就有序了。因为每次调整区间后,左边的最大值,都小于右边的最小值,当递归到只有一个元素时,每一个小区间的值就是整体递增的了。

如何调整区间

调整区间有很多方式,这里介绍两种常见方式 这里我们设所有的分界点为$x$ 第一种是一种暴力的思想,需要两个额外的数组空间

  1. 开两个额外的数组设为$a[]$, $b[]$
  2. 扫描整个数组,$q[l \sim r]$,则有两种情况,如果$q[i] ≤ x$,则把 $x$ 放到 $a[]$ 数组中,如果$q[i]≥x$,则把 $x$ 放到 $b[]$ 数组中。
  3. 然后先把 $a[]$ 数组中的数放到 $q[]$ 数组中,再把 $b[]$ 数组中的数放到 $q[]$ 数组中。 则即可满足调整区间,虽然需要两个额外的数组空间,但是仍然可以线性的调整区间,如果忘记最好的做法,可以用这种方式来代替 ::: hljs-left

调整区间的第二种做法是基于双指针算法的思想,设置两个指针我们设为$i,j$,让$i$指针指向左端点$l$,让$j$指针指向右端点$r$。 首先让$i$指针向右移动,每动一次,与当前位置的数比较一次,当当前位置的数$≤x$,则证明当前位置的数呆在合法位置,否则如果当前位置的数$≥x$,则$i$指针停止。$j$指针同理反着来看,应该向左移动,如果当前位置的数$≥x$,则证明该数处于合法位置,则$j$指针继续向左移,直到遇到一个数$≤x$,则证明该位置的数不合法,此时两个指针分别指向两个不合法的数,只要交换两个位置的数即可让两数分别合法。 证明上述方法的正确性: 因为$i$指针左边的所有数都小于等于$x$,否则如果大于等于$x$,则$i$指针应该停止,同理$j$指针右边的所有数也一定大于等于$x$,然后交换到两边的数应该分别满足其对应的性质,这样当两个指针相遇时,该区间一定是满足分界点左边小于等于分界点,右边大于等于分界点 :::

快排模板

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int q[N];
int n;
void quick_sort(int l, int r, int q[])
{
    if (l >= r)
        return;
    int x = q[l + r >> 1], i = l - 1, j = 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]);
    }
    quick_sort(l, j, q);
    quick_sort(j + 1, r, q);
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++)
        cin >> q[i];
    quick_sort(0, n - 1, q);
    for (int i = 0; i < n; i ++)
        cout << q[i] << " ";
    return 0;
}

代码注意点

代码第十行意为,当数组里没有元素或只有一个元素的时候不需要排序,直接返回 代码第十二行,之所以要让$l-1$ 和 $r+1$是因为下边用do while,即不管什么情况,两个指针都会先移动一次,因此为了判断第一个元素,要将指针先偏移出去。 代码第十七行,不判断直接交换不行吗?是不可以的,因为最后一次交换很有可能两个指针已经超过彼此,如果还交换的话,就会把调整好的区间换回来。 代码第二十,二十一行,这里用$i$指针表示可以吗?是可以的,不过要换成$i - 1$,和$i$ 并且注意,如果要是这里写$i$的话,分界点一定不能取$l$,否则会有边界问题。 例如:排序序列设为1, 2。 则最开始$i$指向1,$j$指向2 取第一个点为分界点,则$i$指针不满足,停在1的位置,而$j$指针是满足大于分界点,则$j$移动到1这个点,此时$i$和$j$相遇则会停下来,此时$i$等于0,所以带入调用$quick_sort(0, i-1)$,调用会返回,而$quick_sort(i, r)$是$quick_sort(0,1)$,我们会发现,这个调用永远都是这样,因此会死循环调用,同理当我们这里用$j$时,分界点不能取$q[r]$,所以综合考虑还是建议取中点

参考

acwing算法基础课