2020/9/22 Acwing-快速排序算法

题目说明

给定你一个长度为n的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在1~109范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

题目分析

主要思路

快速排序算法主要的思想是分治思想,把大问题分成小问题,小问题又分为更小的问题,一直到最简单的问题,这样最简单的问题解决了,大问题也解决了。
对于本题,主要的思路是将一个大的数组排序问题转化为对两个较小个数组排序,并且这两个数组满足:一个数组的数全部大于等于某个数,另一个数组的全部小于等于某个数,这样就这两个数组的大致顺序就已经确定了(小于的在前,大于的灾后),剩下的就是对这两个小数组的排序了。
那么有了思路要如何操作呢?
方法是在数组两边各设一个指针,左指针向右遍历,只要指向的数比 比较的数小就一直向右走,当出现指向的数大于或等于 被比较的数时就立马停下来(隐藏信息:这个数的位置错了,应该抛到右边去!),同理,右边指针也要一直向左遍历,直到发现了不比 被比较的数大的数,就立马停下来(隐藏信息:这个数的位置错了,应该抛到左边去)。现在,有一个数想去右边,有一个数想去左边,将他们交换即可。(注意,交换的前提是指针的先后不能换,指针先后改变意味着排序已经完成,不能再交换了!)交换之后,继续进行上述操作,直到指针顺序交换结束。
任何递归/分治都一定要有边界条件,否则函数会一直调用,最终会导致栈溢出。这里的边界条件是,当调用函数时传入的角标头和尾相同,会直接return。为什么这个条件是合理的边界条件?因为整个函数在不停的划分两部分(满足一定条件的两部分),划分到最后时,就只会剩下俩个数

  • 顺序正确时:左指针不动,右指针左移,两指针重合。(可以结束了)
  • 顺序相反时:左指针不动,右指针不动,左右顺序不变,进行调换,从而变为顺序正确的情况了。
    这样,问题就解决了。

源代码

public static void sort(int q[], int l, int r){
        if (l == r) return;

        int i = l - 1, j = r + 1, x = q[l + r >> 1];//随意选取的是中间的数 这里不去改变l的值是为了之后的迭代,这里的x与q[l]是一样的,在最后的边界探讨上都是不能用i.建议选用中间角标的数,因为这样当数组有序时提高递归效率(如果选两边则每次递归只解决了一个数)
                                                    //也需要使用边界
        while (i < j)
        {
            do i ++ ; while (q[i] < x); //先动指针后判断的目的是为了当指针恰好指向x的值时保证程序继续向下运行,
                                        //指针必须要能接着往下走
            do j -- ; while (q[j] > x);
            if (i < j) {        //最后的终止情况有可能是指针重合在x上i=j,或者指针交错过i>j(也就是说,最终时刻
                                //j一定是在i的前面(或者相同),也就是说j一定是准的
                int temp=q[i];
                q[i]=q[j];
                q[j]=temp;
            }
        }
        sort(q, l, j);sort(q, j+1, r);


    }
}

1.代码内有几个特殊的点需要特别留意下,一个是在设立指针时,选择的是传入的左右角标向外一个元素,并在遍历时配合使用do-while语句,这样做的目的是能够让遍历一直进行下去,如果使用普通的while语句,左右两边都碰到一个相同的数(等于被比较的数),那么左判断向右抛,右判断向左抛,抛来抛去形成死循环。如果do-while,抛完后第一件要做的就是指针移动,这样就避开了死循环了。
2.在迭代的时候,作为迭代函数的传入参数,小数组选用的右边界是原来的右指针;大数组选用的左边界是原来的右指针+1,就是为了防止出现边界问题,出现无限递归。

sort(q, l, j);
sort(q, j+1, r);

至于原因,是因为当排序进行到最后,两指针在经过无数次的交换后,左右指针会发生先后的交换,这意味着遍历的终止,而最后两指针停下来的位置,都是从前只后第一个不满足条件的(与前面的变化其实相同,唯一不同的不过是因为指针交叉,所以这次不会再交换值了),因此,为了找到两个符合规则的数组,前一个数组可以包括右指针(因为右指针不满足右边,就一定可以放在左边),后一个数组起始在其后即可。 同理的,后一个数组的起始可以是左指针,前一个数组的结尾是左指针前一个。但是要注意,两种都可以,但是要匹配对应的x值,使用左指针x值就不能用左指针来作为边界;使用右指针x值就不能用右指针作为边界。

小思考

1.两个指针的判断条件,其实都是不包括等号条件的,也就是不是纯大于/小于就被标记要交换,但是最后的数组结果其实两边都是有等于比较数x的存在的。这是因为x这个数左边不要,就被抛到右边,而右边不会判断,而是直接保留;另一边同理。这也就是为什么要用do-while,就是为了让抛过去的数直接移动指针,判断的事在后面。
2.为什么判断条件里写while(p[x]>x)而不是>=呢?
因为单纯的大于会让指针停在边界处,而不向前无限走;否则,对于正确序列如{1,2,3}(如果取x=1),左指针会停在2处,而右指针就会无限向左走,一个等号的去掉会让右指针停在1处。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值