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

文章目录

  • 前言
  • 快速排序的基本过程
  • 两种快排的实现方法
  • 总结


前言


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

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

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

快速排序的基本过程

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

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

我们以关键序列{26,53,48,15,13,48,32,15}看一下一次划分的过程:
算法通过村第十关-快排|青铜笔记|快排也没那么难_第1张图片
上面圈起来的表示当前已经被赋予给了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))

总结

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

你可能感兴趣的:(算法集训营,算法笔记,快速排序,前序遍历,对撞双直针问题,Java)