【数据结构】排序 —— 快速排序(quickSort)

Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~

🌱🌱个人主页:奋斗的明志

🌱🌱所属专栏:数据结构、LeetCode专栏

📚本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。

快速排序


前言

一、快速排序

1.概念

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

2.快排递归实现的主框架


java 复制代码
// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int[] array, int left, int right) {
    if (right - left <= 1) return;
// 按照基准值对array数组的 [left, right)区间中的元素进行划分
    int div = partion(array, left, right);
// 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right) // 递归排[left, div)
    QuickSort(array, left, div);
// 递归排[div+1, right)
    QuickSort(array, div + 1, right);
}

与二叉树前序遍历规则非常像,在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。


3.Hoare法(方法一)

【图解】




【主要代码展示】

java 复制代码
/**
     * Hoare法
     * @param array
     * @param left
     * @param right
     * @return
     */
    private static int partitionHoare(int[] array, int left, int right) {
        //这里以最左边的元素为基准元素
        int tmp = array[left];
        //要先记录下来 left 的值,因为在下面操作过程中,left 的值会变
        int i = left;
        while (left < right){

            while (left < right && array[right] >= tmp){
                right--;
            }

            while (left < right && array[left] <= tmp){
                left++;
            }

            //进行交换
            swap(array,left,right);
        }
        //将基准元素交换到中间
        swap(array,i,left);
        //返回此时基准元素的下标
        return left;
    }

【测试这个方法】

java 复制代码
public static void testQuickArray(int[] array){
        int[] tmpArray = Arrays.copyOf(array,2 * array.length);
        long startTime = System.currentTimeMillis();
        Sort.quickSort(tmpArray);
        long endTime = System.currentTimeMillis();
        System.out.println("快速排序所用时间:" + (endTime - startTime));
    }
java 复制代码
   public static void main(String[] args) {
        System.out.println("给100000个数据");
        int[] array = new int[10_0000];
        inorderArray(array);
//        notInorderArray(array);
//        initArray(array);

        testInsertArray(array);
        testShellArray(array);
        testSelectArray(array);
        testQuickArray(array);

    }

栈会溢出,可以修改idea里面的配置

因为这个方法时间复杂度:

* 最好情况下:O(N*logN)

* 最坏情况下:O(N^2) 有序 / 逆序

* 空间复杂度:

* 最好情况下:O(logN)

* 最坏情况下:O(N)

* 稳定性:

* 不稳定的排序


4.挖坑法(方法二)

【图解】



【主要代码展示】


java 复制代码
    /**
     * 挖坑法
     * @param array
     * @param left
     * @param right
     * @return
     */
    public static int partition(int[] array,int left,int right){
        int tmp = array[left];
        while (left < right){

            while (left < right && array[right] >= tmp){
                right--;
            }
            array[left] = array[right];

            while (left < right && array[left] <= tmp){
                left++;
            }
            array[right] = array[left];
        }

        array[left] = tmp;
        return left;
    }

【测试这个方法】

java 复制代码
public static void main(String[] args) {
        int[] array = {3,5,2,10,9,8,17};
        Sort.quickSort(array);
        System.out.println(Arrays.toString(array));
    }


5.前后指针法(方法三)

【写法一】

java 复制代码
/**
     * 前后指针法
     */
    private static int partition2(int[] array, int left, int right) {
        int prev = left;
        int cur = left + 1;
        while (cur <= right) {
            if (array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array, cur, prev);
            }
            cur++;
        }
        swap(array, prev, left);
        return prev;
    }

【写法二】

java 复制代码
private static int partition3(int[] array, int left, int right) {
        int d = left + 1;
        int pivot = array[left];
        for (int i = left + 1; i <= right; i++) {
            if (array[i] < pivot) {
                swap(array, i, d);
                d++;
            }
        }
        swap(array, d - 1, left);

        return d - 1;
    }

二、快速排序优化

1.三数取中法选key

java 复制代码
private static int middleNum(int[] array, int left,int right){
        //中间位置的下标
        int mid = left + ((right - left) >> 1);
        //怎么拿到这个下标对应的中间数字呢

        if (array[left] < array[right]){
            if (array[mid] < array[left]){
                return left;
            }else if (array[right] < array[mid]){
                return right;
            }else {
                return mid;
            }
        }else {
            if (array[right] > array[mid]){
                return right;
            }else if (array[mid] > array[left]){
                return left;
            }else {
                return mid;
            }
        }
    }

2.递归到小的子区间时,可以考虑使用插入排序



java 复制代码
public static void insertSort2(int[] array,int start,int end) {
        //要求数组不为空
        for (int i = start + 1; i <= end; i++) {
            //将 i 下标的值放入 tmp 中
            int tmp = array[i];
            int j = i - 1;
            for (; j >= start; j--) {
                //j下标的值和i下标的值进行比较
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                } else {
//                    array[j + 1] = tmp;
                    break;
                }
            }
            array[j + 1] = tmp;
        }
    }

3.快速排序非递归

java 复制代码
    /**
     * 快排非递归
     */
    public static void quickSortNor(int[] array){
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = array.length - 1;
        int pivot = partition(array,left,right);
        if (pivot - 1 > left){
            stack.push(left);
            stack.push(pivot - 1);
        }
        if (pivot + 1 < right){
            stack.push(right);
            stack.push(pivot + 1);
        }
        while (!stack.isEmpty()){
            right = stack.pop();
            left = stack.pop();
            pivot = partition(array,left,right);
            if (pivot - 1 > left){
                stack.push(left);
                stack.push(pivot - 1);
            }

            if (pivot + 1 < right){
                stack.push(right);
                stack.push(pivot + 1);
            }
        }
    }

三、完整代码

java 复制代码
public static void quickSort(int[] array) {
        //首先要对待排序的元素进行划分
        //找到一个点,使左边比这个点小,右边比这个点大
        //可以以最左边或者最右边的元素作为基准
        //调用下面的方法进行划分
        quick(array, 0, array.length - 1);
    }

    private static void quick(int[] array, int start, int end) {
        if (start >= end) {
            return;
        }

        if ((end - start + 1) <= 19) {
            insertSort2(array, start, end);
        }

        //找基准之前,先对三数取中
        int index = middleNum(array, start, end);
        //找到中间元素下标之后
        //进行交换
        //把中间大的下标和第一个数字交换

        swap(array, index, start);

        //找到划分之后 基准元素的下标
        int pivot = partition(array, start, end);
        //以基准元素为点,又分左右两边
        //类似与二叉树的前序遍历
        quick(array, start, pivot - 1);//左边
        quick(array, pivot + 1, end);//右边
    }

    public static void insertSort2(int[] array, int start, int end) {
        //要求数组不为空
        for (int i = start + 1; i <= end; i++) {
            //将 i 下标的值放入 tmp 中
            int tmp = array[i];
            int j = i - 1;
            for (; j >= start; j--) {
                //j下标的值和i下标的值进行比较
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                } else {
//                    array[j + 1] = tmp;
                    break;
                }
            }
            array[j + 1] = tmp;
        }
    }

    private static int middleNum(int[] array, int left, int right) {
        //中间位置的下标
        int mid = left + ((right - left) >> 1);
        //怎么拿到这个下标对应的中间数字呢

        if (array[left] < array[right]) {
            if (array[mid] < array[left]) {
                return left;
            } else if (array[right] < array[mid]) {
                return right;
            } else {
                return mid;
            }
        } else {
            if (array[right] > array[mid]) {
                return right;
            } else if (array[mid] > array[left]) {
                return left;
            } else {
                return mid;
            }
        }
    }

    /**
     * Hoare法
     *
     * @param array
     * @param left
     * @param right
     * @return
     */
    private static int partitionHoare(int[] array, int left, int right) {
        //这里以最左边的元素为基准元素
        int tmp = array[left];
        //要先记录下来 left 的值,因为在下面操作过程中,left 的值会变
        int i = left;
        while (left < right) {

            while (left < right && array[right] >= tmp) {
                right--;
            }

            while (left < right && array[left] <= tmp) {
                left++;
            }

            //进行交换
            swap(array, left, right);
        }
        //将基准元素交换到中间
        swap(array, i, left);
        //返回此时基准元素的下标
        return left;
    }


    /**
     * 挖坑法
     *
     * @param array
     * @param left
     * @param right
     * @return
     */
    public static int partition(int[] array, int left, int right) {
        int tmp = array[left];
        while (left < right) {

            while (left < right && array[right] >= tmp) {
                right--;
            }
            if (left >= right) {
                break;
            }
            array[left] = array[right];

            while (left < right && array[left] <= tmp) {
                left++;
            }
            if (left >= right) {
                break;
            }
            array[right] = array[left];
        }

        array[left] = tmp;
        return left;
    }

    /**
     * 前后指针法
     */
    private static int partition2(int[] array, int left, int right) {
        int prev = left;
        int cur = left + 1;
        while (cur <= right) {
            if (array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array, cur, prev);
            }
            cur++;
        }
        swap(array, prev, left);
        return prev;
    }

    private static int partition3(int[] array, int left, int right) {
        int d = left + 1;
        int pivot = array[left];
        for (int i = left + 1; i <= right; i++) {
            if (array[i] < pivot) {
                swap(array, i, d);
                d++;
            }
        }
        swap(array, d - 1, left);

        return d - 1;
    }

总结


  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度: O(N*logN)
  1. 空间复杂度: O(logN)
  2. 稳定性:不稳定

相关推荐
朦胧之11 小时前
AI 编程-老项目改造篇
java·前端·后端
程序猿大帅15 小时前
别再只当调包侠了:用 Spring AI 落地 Function Calling,我被大模型硬生生砸出了三个大坑
java
程序员晓琪16 小时前
约定大于配置:基于 Java 包名自动生成 API 版本路由的最佳实践
java·spring boot·后端
Flittly16 小时前
【AgentScope Java新手村系列】(11)中断与恢复
java·spring boot·spring
众少成多积小致巨17 小时前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++
东坡白菜17 小时前
破局全栈:前端开发的Java入门实战记录—JPA(2)
java·后端
SimonKing1 天前
艹,维护AI写的代码,我心态崩了......
java·后端·程序员
用户298698530141 天前
Java Word 文档样式进阶:段落与文本背景色设置完全指南
java·后端
小bo波2 天前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
nanxun8863 天前
记一次诡异的 Docker 容器"串包"故障排查
java