分治算法之快速排序

老年选手居然一直不会写快速排序这样的程序,尴尬。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,a[1000001];
void asort(int x,int y)
{
    int w=a[(x+y)>>1];//确定中点
    if(x >= y) return ;
    int i=x,j=y;
    while(i<=j)//自己试的时候等号取不取好像都对
    {
        //找到需要交换的值
        while(a[i]<w) i++;
        //上面只能是小于,不能有等于,否则如果没有比基准数大的数,就会一直加到很大
        //这样的话不必考虑越界的问题,因为如果这是第一次查找,至少我们可以找到w这个数,
        //而如果不是第一次查找,因为之前已经把一个比w大或等于的数换到了后面,所以i一定会
        //碰到这个位置,这样也会停下来 
        while(a[j]>w) j--;
        //同样上面只能是大于,否则如果没有比基准数小的数就会减到负 
        if(i<=j)
        {//注意这里必须是小于等于,如果没有等于可能因为i,j大小改变不了而永远卡在这里 
            swap(a[i],a[j]);
            ++i;--j;//防止交换的两个值相等,while持续进行下去 
        }
    }
//    cout<<endl<<i<<j;
    if(x<j) asort(x,j);//分治 
    if(y>i) asort(i,y);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    asort(1,n);
    for(int i=1;i<=n;i++)
    {
        if(i==1) printf("%d",a[i]);
        else printf(" %d",a[i]);
    }
    cout<<endl;
    return 0;
}

注意这一句话:int w=a[(x+y)>>1];//确定中点
之前我是这么写的:int m=(x+y)>>1;
并且调用的时候用的是a[m],卡了好久,最后才注意到这样一个问题:
对于1 4 8 9 6 5这样一组数据,一开始就会交换8和5,然后呢?然后a[m]就变了!!!

上面这个程序如果理解为先找了中间的数作为基准数,把小于基准数的数放在左边,大于的在右边那么我们手动模拟上面的那组数据,发现最后基准数8居然到了最右边,比他大的9却在左边。思考了很久终于有了一个合理的解释,即所谓的把比基准数大或等于的数放到基准数的右边,其实并不是真的在数组中放到基准数的右边,而是把他们放进一个集合中。这个集合是没有顺序的,且他和左边比8小的数的集合之间有一条抽象的分割线,这条线只有在本层处理执行完后才可以被确认,因为只有在这个时候才成功的通过查找和交换操作把两类数完全分开了。所以说我们不能真的去在意顺序。

还有一个思路就是说把区间第一个值作为基准数,然后找到基准数该去的地方,这个地方满足的条件是在他的左边的数都比基准数小,右边的都大。为了得到这样一个地方,我们需要一个i和j初始时指向区间的左端点和右端点,在向中间移动的过程中把让i指向比基准数大的数,j指向小的数,每次找到这么一组数,并交换。
当最后i,j相遇,因为循环条件的问题,他们会错开并且最终指向相邻的位置,其中左边的也就是j指向的是比基准数小的数的区间的最后一位,i指向的则是比基准数大的数所在区间的第一个数。很明显这中间的位置就是基准数应该呆的地方,但是怎么将他移过去呢?很简单,只要将基准数与a[j]交换即可。具体代码实现如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,a[1000001];
void asort(int x,int y)
{
    int w=a[x];
    if(x >= y) return ;
    if(x == y-1)
    {
        if(a[x] > a[y])
        {
            swap(a[x],a[y]);

        }
        return;
    }
    int i=x+1,j=y;
    while(i<=j)
    {
        //找到需要交换的值 
        while(a[i]<=w&&i<=y) i++;
        while(a[j]>=w&&j>=x+1) j--;
/*这里必须用大于等于,因为我们期望使i与j出现“j,i”这样的顺序,以此确定基准数应该出现的位置
如果没有等于,可能出现的情况就是如果i与j同时指向一个和基准数相等的数,
那么就会导致循环一直持续下去*/ 
//        cout<<i<<' '<<j<<endl;
        //实现中点左边的值都比中值小,所以把比a[m]大的值交换到右边 
        if(i<=j)
        {
            swap(a[i],a[j]);//这里不能i++;j--; 
        }
        else
        {
            swap(a[x],a[j]);
            if(x < j-1) asort(x,j-1);
//            cout<<endl<<i<<" "<<y<<"orz";
            if(i < y) asort(i,y);
        }
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    asort(1,n);
    for(int i=1;i<=n;i++)
    {
        if(i==1) printf("%d",a[i]);
        else printf(" %d",a[i]);
    }
    cout<<endl;
    return 0;
}

但是这个代码有一个我始终没找到的bug,在洛谷上提交这个代码的时候总是有两个点TLE,我也不知道哪里会导致超时,如果有小伙伴看出来的话帮忙发一下邮件(我的邮箱:15762997780@163.com),但我总感觉思路是对的。

总结:

由此上面的分析可以看出,分治需要注意的就是我们到底是按照什么标准分的以及分治时的分割线是谁。
还有一个是所有算法都应该注意的,就是边界问题,等于能否取到一定想明白,否则会出现超时,越界等一系列问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值