快速排序

快速排序

我居然现在还不会快排。

第一种写法:

// luogu-judger-enable-o2
#include <cstdio>
using namespace std;

const int maxn=1e5+5;
int n, a[maxn];

void swap(int &a, int &b){
    int tmp=a; a=b; b=tmp; }

void sort(int l, int r){ // 代表[l,r)
    //两哨兵太难写了,一哨兵大X好
    if (l+1>=r) return;
    int std=a[l], now=l;
    for (int i=l+1; i<r; ++i) //比std小的都排到左端
        if (a[i]<std) swap(a[i], a[++now]); //能取等号?
    swap(a[l], a[now]);
    sort(l, now); sort(now+1, r); //now+1,避免死循环
}

int main(){
    scanf("%d", &n);
    for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
    sort(1, n+1);
    for (int i=1; i<=n; ++i) printf("%d ", a[i]);
    return 0;
}

这种单哨兵的方法,和双哨兵且有一个哨兵带等号的方法一样,都有一个致命缺陷——当序列中数字全部相同时,时间退化到n^2。这就是交这个程序到洛谷上tle的原因。于是我找到了写法二——

#include <cstdio>
using namespace std;

const int maxn=1e5+5;
int n, a[maxn];

void swap(int &a, int &b){
    int tmp=a; a=b; b=tmp; }

void sort(int l, int r){ //左闭右开区间
    if (l+1>=r) return;
    int std=a[(l+r)>>1];
    int i=l, j=r-1;
    while (i<=j){ //避免撞车使i和j没有到预定位置,加上等号
        //注意两个while不能带等号,不然i或j可能一冲到底
        //,造成区间大小不变,陷入死循环
        while (a[i]<std) ++i;
        while (a[j]>std) --j;
        //可能i和j都冲过头了,要避免这种情况下交换
        if (i<=j){ //同理避免撞车,加上等号
            swap(a[i], a[j]);
            //若来到了与基准值相等的区域,就需要用蛮力推进了
            ++i; --j;
        }
    }
    sort(l, j+1); sort(i, r);
}

int main(){
    scanf("%d", &n);
    for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
    sort(1, n+1);
    for (int i=1; i<=n; ++i) printf("%d ", a[i]);
    return 0;
}

大部分注意点但都写在代码中了,我来解释一下。首先,用左闭右开区间,是因为看了刘汝佳的书,书中说这种区间表示法更好。while中不能带等号,不然要么死循环,要么超时。由于区间的划分时按照i和j来决定的,所以i必须正好在基准值右侧,j必须正好在基准值左侧,才能保证在长度为二的区间中不出现循环情况。因此,i<j的判定要加上等号,这样j一定小于i。

然而,后来我又在寻思如何用第二种写法求第k小数。我原本以为第二种写法会漏过与基准相同的数,但这其实一点关系都没有,不会影响结果(仔细想一想)。不过第二种写法还是比较难写的。

还有,第一种写法是可以改进的。只要找出等于基准值的区间,然后取中值即可。虽然较慢(常数在第二种写法的1.5倍左右),但是个人很喜欢这种写法。

#include <cstdio>
using namespace std;

const int maxn=1e5+5;
int n, a[maxn];

void swap(int &a, int &b){
    int tmp=a; a=b; b=tmp; }

void sort(int l, int r){ // 代表[l,r)
    //两哨兵太难写了,一哨兵大X好
    if (l+1>=r) return;
    int std=a[(l+r)>>1], now=l;
    for (int i=l; i<r; ++i) //比std小的都排到左端
        if (a[i]<std) swap(a[i], a[now++]);
    int tmp=now; //[l,tmp)是比std小的
    for (int i=tmp; i<r; ++i) //和std相等的都排到右端
        if (a[i]==std) swap(a[i], a[now++]);
    //[tmp,now)是和std相等的
    sort(l, tmp); sort(now, r);
}

int main(){
    scanf("%d", &n);
    for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
    sort(1, n+1);
    for (int i=1; i<=n; ++i) printf("%d ", a[i]);
    return 0;
}
posted @ 2018-02-14 12:36  pechpo  阅读(195)  评论(0编辑  收藏  举报