【排序】快速排序|Java 刷题打卡

285 阅读2分钟

本文正在参加「Java主题月 - Java 刷题打卡」,详情查看活动链接

一、前言

快速排序:是由东尼·霍尔提出的一种高效的排序算法,简称快排。

可以用 3 个步骤 6 个字来概括:选基、分割、递归:

  • 选基:首先挑选基准值

  • 分割:分割数组,把小于基准值的元素放到基准值前面,大于基准值的元素放到基准值后面

  • 递归:递归地对小于基准值的子序列和大于基准值的子序列进行排序

举栗,动图如下:

quickSort.gif

为什么快排会比冒泡等快?

因为其是跳跃式交换,即每次交换相隔较远的 2个元素。



二、知识点

知识点,如下:

  1. 时间复杂度
  2. 逆序对
  3. 实现可分为两种:
    1. Lomuto 分割方法:实现简单,不易出错,效率差一些
    2. 霍尔分割方法:减少交换

(1)时间复杂度

  • 最好情况:时间复杂度 O(N * logN)

  • 最坏情况:时间复杂度 O(N ^ 2),增量元素不互质,则小增量可能根本不起作用

  • 稳定性:不稳定。

    例如,原数组 {4, 1, 4}

    不稳定:排序过程中,第二个 4 排在了 第一个 4 前面。

排序总图,如图: 2021-05-2318-41-48.png


(2)逆序对

逆序对(inversion:对于下标 i < j,如果 arr[i] > a[j],则称 (i, j) 是一对逆序对。

举个栗子,序列 {34, 8, 64, 51, 32, 21} 有多少逆序对?

9对:

(34, 8), (34, 32), (34, 21), (64, 51), 

(64, 32), (64, 21), (51, 32), (51, 21), (32, 21)

可得定理:

  • 定理:任意 N 个不同元素组成的序列平均具有 N(N-1)/4 个逆序对
  • 定理:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为 O(N^2)

那么逆序对,有什么用呢?

  1. 代表了,需要交换的次数。

  2. 为提高算法效率提供基础

那么要提高算法效率,必须:

  1. 每次消去不止 1个逆序对

  2. 每次交换相隔较远的 2个元素


(3)实现

1)Lomuto 分割方法

实现简单,不易出错,效率差一些。

基准线:以最高点

public class QuickSort {

    // Time: O(n * log(n)), Space: O(n)
    public void lomutoSort(int [] arr) {

        if (arr == null || arr.length == 0) return;
        lomutoSort(arr, 0, arr.length - 1);
    }

    private void lomutoSort(int [] arr, int low, int high) {
        if (low < high) {
            int k = lomutoPartition(arr, low, high);
            lomutoSort(arr, low, k - 1);
            lomutoSort(arr, k + 1, high);
        }
    }

    private int lomutoPartition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low;
        for (int j = low; j < high; ++j) {
            if (arr[j] < pivot) {
                swap(arr, i, high);
                ++i;
            }
        }
        swap(arr, i, high);
        return i;
    }

    private void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

2)霍尔分割方法

基准线:以中间点

public class QuickSort {

    // Time: O(n * log(n)), Space: O(n)
    public void hoareSort(int [] arr) {

        if (arr == null || arr.length == 0) return;
        hoareSort(arr, 0, arr.length - 1);
    }

    private void hoareSort(int [] arr, int low, int high) {
        if (low < high) {
            int k = hoarePartition(arr, low, high);
            hoareSort(arr, low, k);
            hoareSort(arr, k + 1, high);
        }
    }

    private int hoarePartition(int [] arr, int low, int high) {
        int pivot = arr[low + (high - low) / 2];
        int i = low, j = high;
        while (true) {
            while (arr[i] < pivot) ++i;
            while (arr[j] > pivot) --j;
            if (i >= j) return j;
            swap(arr, i++, j--);
        }
    }
    
    private void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}