首页 > 技术文章 > 快速排序模板

G-H-Y 2020-12-21 20:15 原文

快排核心问题

  1. 快排最关键的位置就是 关键点的选取和递归排序时子区间端点的选择,可以简记为

关键点选左端点,子区间端点要看右指针
关键点选右端点,子区间端点要看左指针
关键点选中间,子区间端点随意选
原因可以理解为处理特殊情况(特例见代码)

  1. 在分子区间端点时,为什么是\([l, j], [j + 1, r]\)\([l, i - 1], [i, r]\)
    首先要明确我们的目标是保证关键点左侧都是 <= 它的数,关键点右侧都是 >= 它的数。
    我们看j点,最终指向的应该是 <= 关键点的数据,由于无法区分是小于还是等于,所以直接分到左半边,所以是\([l, j]\),自然就对应了\([j + 1, r]\)
    同理可得\(i\)这种划分方法的原因
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;

int n;
int a[N];

void quick_sort(int a[], int l, int r)
{
    if (l >= r) return ;
    
    int x = a[l + r >> 1], i = l - 1, j = r + 1;
    
    while(i < j)
    {
        while (a[++ i] < x); // 按照实际思路,x左侧应该为<=x的值,但是这么做相当于是<x的值,这样说就说不通了,但是如果写为<=假设对于14253是无法得到正确结果的,这里的边界情况较多,不做深入追究
        while (a[-- j] > x);
        if (i < j) swap(a[i], a[j]);
    }
    
    // 测试x的选取的数据可以是:
    // 2
    // 1 2
    // x = a[l]; || x = a[l + r >> 1];
    quick_sort(a, l, j);
    quick_sort(a, j + 1, r);
    // x = a[r]; || x = a[l + r >> 1];
    // quick_sort(a, l, i - 1);
    // quick_sort(a, i, r);
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; ++ i) cin >> a[i];
    
    quick_sort(a, 0, n - 1);
    
    for (int i = 0; i < n; ++ i) cout << a[i] << " ";
    
    return 0;
}

快速排序引申出的快速选择算法

该算法能够在\(O(n)\)的时间复杂度内找到无序序列中第\(k\)小的数

问题描述

给定一个长度为\(n\)的整数数列,以及一个整数\(k\),请用快速选择算法求出数列从小到大排序后的第\(k\)个数

问题分析

已知一个无序序列,我们选定\(x\)作为分界值,快速排序第一轮之后,序列被分为两半部分,左半边所有数值\(<=x\),设长度为\(sl\);右半边所有数值\(>=x\),设长度为\(sr\)
假设我们想要找得是第\(k\)小的数,如果\(k<=sl\),说明待求值位于左半边,如果\(k>sl\),说明待求值位于右半边。根据该性质,我们只需要递归对应区间进行快速排序,最终即可找到第\(k\)小的数
第1轮快速排序运算次数为\(n\),之后每次快排的期望运算次数依次减半,所以\(时间复杂度 = n + \frac{n}{2} + \frac{n}{4} + \ ... \ = \ n(1 + \frac{1}{2} + \frac{1}{4} + \ ...) <= 2n\),所以时间复杂度为\(O(n)\)

代码实现-手写快速选择

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, k;
int a[N];

int quick_sort(int a[], int l, int r, int k)
{
    if (l >= r) return a[l];
    
    int x = a[l + r >> 1], i = l - 1, j = r + 1;
    while (i < j)
    {
        while (a[++ i] < x);
        while (a[-- j] > x);
        if (i < j) swap(a[i], a[j]);
    }
    
    int sl = j - l + 1;
    if (k <= sl) return quick_sort(a, l, j, k);
    return quick_sort(a, j + 1, r, k - sl);
}
int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; ++ i) cin >> a[i];
    
    cout << quick_sort(a, 0, n - 1, k) << endl;
    
    return 0;
}

代码实现-库函数

在algorithm头文件中存在一个nth_element的函数,同样能够找到第k个元素

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, k;
int a[N];

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; ++ i) cin >> a[i];
    
    nth_element(a, a + k - 1, a + n); // 第k小的值下标为k-1
    
    cout << a[k - 1] << endl; // 第k小的值下标为k-1
    return 0;
}

推荐阅读