快速排序算法

5. 快速排序

5.1 概念

  • 什么是快速排序(Quick Sort)、

    通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

  • 实现步骤

    1. 每一轮排序选择一个基准点(point)进行分区,让小于基准点的元素进入一个分区,大于基准点的元素进入另一个分区;当分区完成时,基准点元素的位置就是其在整个数组排序好之后的位置。(即这一轮结果确定了基准点元素的最终位置

    2. 在子分区中重复步骤1的过程,直至分区元素个数小于等于1,这体现了**分而治之(divide-and-conquer,简称D and C)**的思想。

5.2 单边循环快排

  • 单边循环快排实现方式:

    1. 最右边的元素(数组末尾元素)作为point基准点元素
    2. j 指针负责找到比基准点小的元素,一旦找到则与i进行交换
    3. i 指针有两个作用:
      • 维护小于基准点元素的边界
      • 每次交换的目标索引
    4. 最后基准点与 i 交换,i 即为分区位置
  • 代码实现

    public class QuickSort01 {
        public static void main(String[] args) {
            int[] arr = {5, 3, 6, 1, 2, 4, 9, 8, 7};
            quick(arr, 0, arr.length - 1);
        }
    
        public static void quick(int[] arr, int left, int right) {
            if (left >= right) {
                return;
            }
            int p = partition(arr, left, right);
            quick(arr, left, p - 1);
            quick(arr, p + 1, right);
        }
    
        public static int partition(int[] arr, int left, int right) {
            //定义一个基准点元素,通常为数组的最后一个元素
            int point = arr[right];
            //定义一个元素待交换的位置索引
            int i = left;
            for (int j = left; j < right; j++) {
                if (arr[j] < point) {
                    if (i != j){
                        swap(arr, i, j);
                    }
                    //每次交换后,待交换元素索引位置右移一位
                    i++;
                }
            }
    
            if (i != right){
                swap(arr, right, i);
            }
            System.out.println(Arrays.toString(arr) + " i=" + i);
            return i;
        }
    
        public static void swap(int[] arr, int i, int j) {
            int t = arr[i];
            arr[i] = arr[j];
            arr[j] = t;
        }
    }
    
  • 输出结果

    .

  • 进一步解析

    1. 事实上,每轮partition的最后结果都是能确定该轮基准点的最终位置。

    2. 调用partition方法,传入的参数为

    • arr:要排序的数组
    • left:元素初始待交换的位置索引,数组从左往右与基准点比较。(第一轮比较为整个数组,故第一个元素索引为0)
    • right:定义基准点元素的索引,默认数组最后一个元素为基准点。(第一轮比较为整个数组,故第一轮的基准点为arr.length - 1 )

    .

    .

    1. 我们可以看到,quick方法是一个递归方法,而退出循环的条件是 left >= right ,即初始待交换的位置索引与基准点元素的索引重合(此时无需比较,该位置就是基准点的正确位置)或基准点元素索引在初始待交换位置索引的左边,而我们的比较时从左往右比的,此时比较无意义,两种情况都可以说明该分区已经排序完毕。在满足退出循环的条件前,quick将一直递归调用,直到每一个分区完毕。

5.3 双边循坏快排

  • 实现方式:

    1. 选择最左边的元素(数组首元素)作为point基准点元素
    2. j 指针负责从右往左找比基准点小的元素,找到后等待 i 指针
      • 在步骤1的条件下,一定要让 j 指针先执行并等待 i 指针的执行完毕。
    3. i 指针负责从左往右找比基准点大的元素。
    4. 当两个指针都找到各自要找的元素后,二者交换
    5. 重复步骤4直到指针 i 与指针 j 相交
    6. 最后基准点与 i(或 j,因为此时 i = j )交换,i 即为分区位置
  • 代码实现

    public class QuickSort02 {
        public static void main(String[] args) {
            int[] arr = {5, 3, 6, 1, 2, 4, 9, 8, 7};
            quick(arr, 0, arr.length - 1);
        }
    
        public static void quick(int[] arr, int left, int right) {
            if (left >= right) {
                return;
            }
            int p = partition(arr, left, right);
            quick(arr, left, p - 1);
            quick(arr, p + 1, right);
        }
    
        public static int partition(int[] arr, int left, int right) {
            //定义基准点元素,双边循环中一般取数组首元素
            int point = arr[left];
            int i = left;
            int j = right;
    
            while (i < j) {
                //j从右往左找比基准点小的元素
                while (i < j && arr[j] > point) {
                    j--;
                }
                //i从左往右找比基准点大的元素
                while (i < j && arr[i] <= point) {
                    i++;
                }
                swap(arr, i, j);
            }
            swap(arr, left, i);
            System.out.println(Arrays.toString(arr) + " i=" + i);
            return i;
        }
    
        public static void swap(int[] arr, int i, int j) {
            int t = arr[i];
            arr[i] = arr[j];
            arr[j] = t;
        }
    }
    
  • 输出结果

    .

  • 进一步解析

    1. 递归方法quick与单边循环快排原理基本一致。

    2. 与单边循环快排不同的是,双边循坏快排定义的基准点为该分区的第一个元素。(int point = arr[left])

    3. 定义一个变量 i,初始值为该分区的首元素位置索引,用来从左往右找比基准点的元素。

    4. 定义一个变量 j,初始值为该分区尾元素的位置索引,用来从右往左找比基准点的元素。

    5. 注意点一:while (i < j && arr[j] > point)与while (i < j && arr[i] <= point)中 i < j 的意义是保证变量 i 的索引不会比 j 大。

    .

    1. 注意点二:while (i < j && arr[i] <= point)中<=而不是<的原因是,如果是<,第一次while循环arr[ i ]的值是等于point基准点的,所以不满足<的条件,直接退出循环,导致 i 指向的是基准点元素,为了让 i++正常执行,需要设置为<=。

    2. 注意点三:在2的前提下,一定要先执行 j,再执行 i。即让 j 先在找到比基准点小的元素,然后再让 i 去找比基准点大的元素。如果先执行 i,再执行 j,会导致这一轮循环完毕后(即 i = j )时,i 与 j 所指向的元素是比基准点大的,此时交换基准点与 i (或 j )的位置,得到的结果是不正确的,即不满足比基准点大的元素在右边,比基准点小的元素在左边

    .

5.4 快速排序的特点

  1. 平均复杂度是O(nlogn),最坏时间复杂度是O(n²)
  2. 数据量较大时,快排的优势比较明细
  3. 快排是不稳定排序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Daylan Du

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值