美文网首页收藏
快速排序(Quick Sort)

快速排序(Quick Sort)

作者: 小波同学 | 来源:发表于2021-11-20 00:19 被阅读0次

一、算法概述

1.1 算法分类

十种常见排序算法可以分为两大类:

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。

  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

1.2 算法复杂度

1.3 相关概念

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

二、快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

2.1 算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

2.2 动图演示

QuickSort

2.3 排序过程

下面以数列a={30,40,60,10,20,50}为例,演示它的快速排序过程(如下图)。


上图只是给出了第1趟快速排序的流程。在第1趟中,设置x=a[i],即x=30。

  • 01、从"右 --> 左"查找小于x的数:找到满足条件的数a[j]=20,此时j=4;然后将a[j]赋值a[i],此时i=0;接着从左往右遍历。

  • 02、从"左 --> 右"查找大于x的数:找到满足条件的数a[i]=40,此时i=1;然后将a[i]赋值a[j],此时j=4;接着从右往左遍历。

  • 03、从"右 --> 左"查找小于x的数:找到满足条件的数a[j]=10,此时j=3;然后将a[j]赋值a[i],此时i=1;接着从左往右遍历。

  • 04、从"左 --> 右"查找大于x的数:找到满足条件的数a[i]=60,此时i=2;然后将a[i]赋值a[j],此时j=3;接着从右往左遍历。

  • 05、从"右 --> 左"查找小于x的数:没有找到满足条件的数。当i>=j时,停止查找;然后将x赋值给a[i]。此趟遍历结束!

  • 按照同样的方法,对子数列进行递归遍历。最后得到有序数组!

三、快速排序分类

快速排序分为:

  • 单边循环快排(lomuto 洛穆托分区方案)
  • 双边循环快排(并不完全等价于hoare霍尔分区方案)
  • hoare霍尔分区方案

这里介绍单边循环快排(lomuto 洛穆托分区方案)双边循环快排(并不完全等价于hoare霍尔分区方案)

3.1 单边循环快排(lomuto 洛穆托分区方案)

  • 1、选择最右元素作为基准点元素
  • 2、j指针负责找到比基准点小的元素, 一旦找到则与i进行交换
  • 3、i指针维护小于基准点元素的边界, 也是每次交换的目标索引
  • 4、最后基准点与i元素交换, i即为分区位置
/**
 * @author: huangyibo
 * @Date: 2021/11/17 18:38
 * @Description: 快速排序——单边循环快排(lomuto 洛穆托分区方案)
 * 1、选择最右元素作为基准点元素
 * 2、j指针负责找到比基准点小的元素, 一旦找到则与i进行交换
 * 3、i指针维护小于基准点元素的边界, 也是每次交换的目标索引
 * 4、最后基准点与i元素交换, i即为分区位置
 */
public class QuickSort1 {

    public static void main(String[] args) {
        int[] arr = {5, 9, 7, 4, 1, 3, 2, 8};

        quickSort(arr,0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }

    public static void quickSort(int[] arr, int left, int right) {
        //当指定区间范围内元素值小于等于1的时候, 停止递归调用
        if(left >= right){
            return;
        }

        //pv 基准点的正确索引值
        int pv = partition(arr, left, right);

        //左边分区的范围确定
        quickSort(arr, left,pv - 1);

        //右边分区的范围确定
        quickSort(arr, pv + 1, right);
    }

    public static int partition(int[] arr, int left, int right) {
        //基准点元素
        int pivot = arr[right];
        int i = left;
        for (int j = i; j < right; j++) {
            if(arr[j] < pivot){
                if(i != j){
                    //i和j不相等的时候才交换元素
                    swap(arr, i, j);
                }
                i++;
            }
        }
        if(i != right){
            //一轮循环之后,基准点元素和小于基准点元素边界值交换
            swap(arr, right, i);
        }

        //返回值代表了基准点元素所在的正确索引,它确定下一轮分区的边界
        return i;
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

3.2 双边循环快排(并不完全等价于hoare霍尔分区方案)

  • 1、选择最左元素作为基准点元素
  • 2、j指针负责从右向左找比基准点小的元素, i指针负责从左向右找比基准点大的元素, 一旦找到二者交换, 直到i、j相交
  • 3、最后基准点与i(此时i与j相等)交换, i即为分区位置
/**
 * @author: huangyibo
 * @Date: 2021/11/17 18:41
 * @Description: 快速排序——双边循环快排(并不完全等价于hoare霍尔分区方案)
 * 1、选择最左元素作为基准点元素
 * 2、j指针负责从右向左找比基准点小的元素, i指针负责从左向右找比基准点大的元素, 一旦找到二者交换, 直到i、j相交
 * 3、最后基准点与i(此时i与j相等)交换, i即为分区位置
 *
 * 特点:
 * 1、平均时间复杂度是O(n ㏒₂n), 最坏时间内复杂度为O(n²)
 * 2、数据量较大时,优势非常明显
 * 3、属于不稳定排序算法
 */
public class QuickSort2 {

    public static void main(String[] args) {
        int[] arr = {5, 9, 7, 4, 6, 1, 3, 2, 8};

        quickSort(arr,0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }

    public static void quickSort(int[] arr, int left, int right) {
        //当指定区间范围内元素值小于等于1的时候, 停止递归调用
        if(left >= right){
            return;
        }

        //pv 基准点的正确索引值
        int pv = partition(arr, left, right);

        //左边分区的范围确定
        quickSort(arr, left,pv - 1);

        //右边分区的范围确定
        quickSort(arr, pv + 1, right);
    }

    public static int partition(int[] arr, int left, int right) {
        //基准点元素
        int pivot = arr[left];
        int i = left;
        int j = right;
        while (i < j){
            //j指针负责从右向左找比基准点小的元素, 必须先从右向左找,先执行j指针
            while(i < j && arr[j] > pivot){
                j--;
            }
            //i指针负责从左向右找比基准点大的元素
            while(i < j && arr[i] <= pivot){
                i++;
            }

            if(i != j){
                //i和j不相等的时候才交换元素
                swap(arr, i, j);
            }
        }
        //一轮循环之后, i指针和j指针相交, 基准点元素和j指针(i、j指针相等)元素交换
        if(j != left){
            swap(arr, left, j);
        }

        //返回值代表了基准点元素所在的正确索引,它确定下一轮分区的边界
        return j;
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

3.3 快速排序特点

  • 1、平均时间复杂度是O(n ㏒₂n), 最坏时间内复杂度为O(n²)
  • 2、数据量较大时,优势非常明显
  • 3、属于不稳定排序算法

参考:
https://www.cnblogs.com/onepixel/articles/7674659.html

https://www.cnblogs.com/skywang12345/p/3596746.html

相关文章

网友评论

    本文标题:快速排序(Quick Sort)

    本文链接:https://www.haomeiwen.com/subject/wtcjtrtx.html