算法通过村第十关-快排|青铜笔记|快排也没那么难


前言


提示:我十分理解沉默的人 我也很喜欢凝视倾听的人 在话语背后-透过模糊的轰隆 美丽的精神剋是苏醒 --勃洛克《我不愿进入到世人中间》

快速排序也是我们算法书中常见的老朋友了,这是很多人面一次挂一次的问题,原因很多,首先快排背身也不是特别好理解,还有就是他复杂化的不够清晰的教程,即使勉强看懂,也不一定能写出来,这次看懂了,下次也不一定能写出来。

快速排序的核心框架是“二叉树的前序遍历 + 对撞型双指针”。我们前面提到过双指针思路:在处理奇数偶数等的情况下会使用连个游标,一个从前先后,一个从后先前。根据结果来决定是都继续移动还是停止等待。快速排序的每一轮都是类似的双指针策略,而递归的过程本质就是二叉树的前序递归调用。

快速排序的基本过程

快速排序是将分治方法运用到了排序问题的经典例子

核心思想:通过一个标记的pivot元素将n个元素的序列划分为左右两个序列left和right,其中left中的元素都比pivot小,right的都比pivot的大,然乎再次堆left和right各自在执行快速排序,将左右子序排列好顺序之后,整个序列就有序了。这里排序进行左右划分的时候是一直划分到序列只包含一个元素的情况,然后再递归返回。

我们以关键序列{26,53,48,15,13,48,32,15}看一下一次划分的过程:
在这里插入图片描述
上面圈起来的表示当前已经被赋予给了pivot或者其它位置,可以空出来放移动来的新元素了。我们可以看到26最终放到了属于自己的位置上,不会再变化。而左侧的都是小于26,右侧的都比26大,因此26的左右两侧可以分别再进行排序。

这一轮过程是什么呢?就是数组增删的时候常用的双指针策略,我们在数组部分讲过,这里推荐看算法通过村第三关-数组白银笔记|数组双指针_师晓峰的博客-CSDN博客,这里不在说明了。这里的每一轮都是相向的双指针,没有什么特殊。

两种快排的实现方法

根据上面的原理,我们一个以写代码,在实现过程中,为了方便实现,会对部分代码过程进行微调。

官方快排思想

看代码展示💕:

   /** 
     * 官方快排思想
     * @param arr
     * @param left
     * @param right
     */
    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            // 初始值
            int i = left - 1;
            // 右侧为哨兵
            int pivot = arr[right];
            // 一轮遍历
            for (int j = left; j < right; j++) {
                if (arr[j] < pivot) {
                    i++; // 找到最左边
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
            // 移动哨兵位置 (这里就是边界点  左边小于pivot 右边大于pivot
            int pivotIndex = i + 1;
            // 记得右侧值了吗
            int temp = arr[pivotIndex];
            arr[pivotIndex] = arr[right];
            arr[right] = temp;
            // 左右开开工 接着遍历
            quickSort(arr, left, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, right);
        }
    }

图片讲解:
在这里插入图片描述

(中序)快排

当然快排的写法有很多种,找出一种适合自己的最好,这里推荐另一种写法。

    /**
     * (中序)快排
     * @param array
     * @param start
     * @param end
     */
    private static void quickSort(int[] array, int start, int end) {
        // 判断跳出条件
        if (start >= end) {
            return;
        }//有的像先处理根节点
        // 对撞型指针
        int left = start, right = end;
        // 取中间值做哨兵
        int pivot = array[(start +end)>>1];
        while(left <= right){
            // 左边小于
            while(left <= right && array[left] < pivot){
                left++;
            }
            // 右边大于
            while(left <= right && array[right] > pivot){
                right--;
            }
            // 交换
            if (left <= right){
                int temp = array[left];
                array[left] = array[right];
                array[right] = temp;
                left++;
                right--;
            }
        }
        //在处理左右子树
        // 分别处理左右两边,左右
        quickSort(array, start, right);
        quickSort(array,left, end);
    }

画图解释:
在这里插入图片描述

复杂度分析:

快速排序的时间复杂度计算比较麻烦。从原理上来看,如果我们选择的pivot每次都恰好在中间,效率极高,但是这种情况是无法保证的,因为我们需要从最好、最坏和中间来分析(折中说哈🤣)

  • 最坏的情况就是每次选择的恰好都是low节点作为pivot,如果也巧是逆序的话,此时时间复杂度为O(n^2)
  • 如果元素恰好都是有序的,则时间复杂度为O(n)
  • 折中的情况是每次选择的都是中间节点,此时序列每次都是长度相等的序列,此时的时间复杂度为O(n(logn))

总结

提示:快速排序;中序遍历;分治思想;双直针问题;前序遍历


如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

如有不理解的地方,欢迎你在评论区给我留言,我都会逐一回复 ~

也欢迎你 关注我 ,喜欢交朋友,喜欢一起探讨问题。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

师晓峰

啤酒饮料矿泉水,你的打赏冲一冲

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值