• 快速排序算法


    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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
    • 输出结果

      .

    • 进一步解析

      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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
    • 输出结果

      .

    • 进一步解析

      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. 快排是不稳定排序
  • 相关阅读:
    确认无疑,.NET 6是迄今为止最快的.NET
    校招零Offer要不要先找实习?
    记录Pcap4j使用的一次异常调查和分析
    按键中断实验
    C语言中的 int main(int argc,char const *argv[])是什么意思?
    面试经验二
    蓝桥杯(迷宫,C++)
    图算法汇总
    【pickle】详解python中的pickle模块(常用函数、示例)
    探讨C#、C++和Java这三门语言在嵌入式的地位
  • 原文地址:https://blog.csdn.net/qq_52248567/article/details/126317981