【算法】快速排序之两种常见的实现

750 阅读6分钟

新的一年就快到了,给大家拜一个早年,祝大家身体健康,万事如意,恭喜发财,学业有成,事业进步

快速排序

常见两种快速排序的套路,大致思想是一致的,同样采用了分治思想,在内部遍历交换的时候有一点区别,为了便于描述,我们把这两种常见的办法称为 方案A 和 方案B

  • 主要体现在标准位(默认为数组首位 index = 0)何时被操作,方案A 是最开始就进行赋值,方案B则是最后两个指针相遇后 (low == high)才对标准位进行赋值。
  • 还有区别在,方案B 是 low 和 high 的值进行调换;方案A 则是单向的赋值,仅仅是 high 的值赋值给 low ,或者 low 赋值给 high。注意:不是调换 low 和 high 的值。

两种办法大同小异,殊途同归。

快速排序主要利用了分治思想,将数组按某个中轴位置划分,中轴左侧的数均小于中轴右侧的数。再对中轴左侧部分的数组进行排序,顺便找出下一个中轴;对中轴右侧部分的数据进行排序,顺便找出中轴;... 直到数组无法再分割为止。

方案 A

图解原理

快排方案1

  1. 起始状态,默认 low = 0 ,high = 数组.length -1

  2. 一般选中 low 的位置作为参考值,这边参考值 refer = 7。这个值需要记住,在最后 low = high 的时候,把 refer 赋值给中轴位置。

  3. 既然参考值选择了 low,那就需要由 high 指针先动了,向左移动,找到一个小于 refer 值的位置停下来,将 high 的值赋值给当前 low 的位置。

  4. 接着,low 指针开始向右移动,移动到大于 refer 的位置停下来,将 low 的值赋给 high 的位置。high 被覆盖的值,并没有丢,而是在 第上一步的 low 位置,而最初的 low 又是 refer

  5. 最终, low = high 的时候,我们暂且把此位置称为中轴,把 refer 赋值给中轴位置。单次操作结束。

此时,中轴左侧的数都是 小于等于 中轴的,而中轴右侧的数总的大于等于 中轴的。我们再把中轴左侧的数组按上面的流程进行排序,此时 low = 0high = 中轴-1。把中轴右侧的数组也进行上述排序。以此递归 ,直到 low < high 不成立。则整个数组完成从小到大排序。

注意,此方案只有单向的赋值,并没有 low 和 high 调换。

代码实现

public static int[] quickSortA(int[] array, int low, int high) {
        if (low < high) {
            int pivot = part(array, low, high);
            quickSortA(array, low, pivot - 1);
            quickSortA(array, pivot + 1, high);
        }
        return array;
    }

    /**
     * @param array
     * @param low
     * @param high
     * @return
     */
    private static int part(int[] array, int low, int high) {
        int refer = array[low];
        while (low < high) {
            while (array[high] >= refer && high > low) {
                high--;
            }
            array[low] = array[high];
            while (array[low] <= refer && high > low) {
                low++;
            }
            array[high] = array[low];
        }
        array[low] = refer;
        return low;
    }

照旧,在完成代码后,走一下该有的单元测试。

方案 B

方案 B 和 方案 A 实现方式非常的相似,看了之后可能有混淆的反作用,万一后续面试答错了那就尴尬了。可以不看 Ctrl + W

学海无涯,回头是岸 = =。

图解原理

快排方案B

  1. 首先,依然是 起始状态,默认 low = 0 ,high = 数组.length -1
  2. 一般选中 low 的位置作为参考值,这边参考值 refer = 7。
  3. 依然是 high 先动,向左移动,遇到 小于 refer 的时候,停下来;
  4. 接着是 low 像右移动,遇到大于 refer 的时候停下来,互换 low 和 high 的值,注意是互换。
  5. 若 low 在右移的过程中一直都没遇到大于 refer 的值,则移动到 low 和 high 相遇。

上述移动过程有一个约束条件,low 和 high 一旦相遇,也就是找到中轴位置,那本次遍历访问就结束。中轴的值和当次数组头(也就是low的最初位置)互换 。

找到中轴位置后,照旧,分别处理中轴左侧和右侧部分的数组,直到不能再被下一个中轴分割(新的 low < high 不成立)

代码实现

public static int[] quickSortB(int[] array, int low, int high) {
        if (low < high) {
            int refer = array[low];
            int tempLow = low;
            int tempHigh = high;
            while (tempLow < tempHigh) {
                while (tempHigh > tempLow && array[tempHigh] >= refer) {
                    tempHigh--;
                }
                while (tempHigh > tempLow && array[tempLow] <= refer) {
                    tempLow++;
                }
                int temp = array[tempHigh];
                array[tempHigh] = array[tempLow];
                array[tempLow] = temp;
            }
            array[low] = array[tempLow];
            array[tempLow] = refer;

            //递归中轴左侧的数组
            quickSortB(array, low, tempHigh - 1);
            //递归中轴右侧的数组
            quickSortB(array, tempHigh + 1, high);
        }

        return array;
    }

思考

为什么每次都是 high 指针先动 ?

答:因为我们选择的基准是 low。

  • 针对方案 A:非常直接。按逻辑,我们每次找到 low 或者 high 满足条件的时候都会进行赋值操作,若从 low 开始动,找到大于基准的位置,就直接将 low 赋值给 high,这时候 high 还没有动过,high 的值是不确定的。 相反,若 high 先动,那 low 的起始值是可以确定的,也就是我们的基准值 refer。相等的,随便换,没有破坏规则。

  • 针对方案 B:high 先动可以保证最后的中轴位置的值一定小于或等于基准值。而中轴最后要和基准位互换值,必然中轴的值必须小于等于基准值才能满足要求。(排序结束中轴左侧的值必须都小于等于中轴值)。

    high 每次都是移动到小于基准值再停下,或者挪到和 low 碰到,low 停的地方值必然小于(上次刚刚和 high 换过位置) 或等于(low 从没移动过)基准值。

    等于基准值的情况是 high 从最开始一路挪过来都没碰到小于基准的坐标,在本次数组的头部直接和 low 相遇。这种情况也满足我们的要求,中轴就是 low 起始位,也就是数组 low 后面的值都是大于或等于它的。

不完全统计,掌握使用 A 的人多于 B,A 也更优雅,不用那个多局部变量存状态。赶紧把方案 B 忘了吧


参考文章:

blog.csdn.net/Luyanc/arti…

blog.csdn.net/shujuelin/a…

招聘广告 🐂

【优酷】杭州团队,长期招聘!!!

   - 前端「急」
   - Java 后端 「爆」
   - 移动端:安卓 & iOS 「热」

办公地点:蚂蚁Z空间。

面试方式:电话&视频优先。

主要有优酷少儿、创新项目等业务,P6/P7 都有。

想试一试的小伙伴,邮件联系 hdtpjhz@163.com