快速排序算法(LeetCode版)

Posted 可持续化发展

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速排序算法(LeetCode版)相关的知识,希望对你有一定的参考价值。


快速排序递归框架

public static void quickSort(int[] arr) {
    quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int start, int end) {
    // 将数组分区,并获得中间值的下标
    int middle = partition(arr, start, end);
    // 对左边区域快速排序
    quickSort(arr, start, middle - 1);
    // 对右边区域快速排序
    quickSort(arr, middle + 1, end);
}
public static int partition(int[] arr, int start, int end) {
    // TODO: 将 arr 从 start 到 end 分区,左边区域比基数小,右边区域比基数大,然后返回中间值的下标
}

partition 意为“划分”,我们期望 partition 函数做的事情是:将 arr 从 start 到 end 这一区间的值分成两个区域,左边区域的每个数都比基数小,右边区域的每个数都比基数大,然后返回中间值的下标。

只要有了这个函数,我们就能写出快速排序的递归函数框架。首先调用 partition 函数得到中间值的下标 middle,然后对左边区域执行快速排序,也就是递归调用 quickSort(arr, start, middle - 1),再对右边区域执行快速排序,也就是递归调用 quickSort(arr, middle + 1, end)。

退出递归的边界条件

当某个区域只剩下一个数字的时候,自然不需要排序了,此时退出递归函数。实际上还有一种情况,就是某个区域只剩下 0 个数字时,也需要退出递归函数。当 middle 等于 start 或者 end 时,就会出现某个区域剩余数字为 0。

public static void quickSort(int[] arr, int start, int end) {
    // 如果区域内的数字少于 2 个,退出递归
    if (start >= end) return;
    // 将数组分区,并获得中间值的下标
    int middle = partition(arr, start, end);
    // 对左边区域快速排序
    quickSort(arr, start, middle - 1);
    // 对右边区域快速排序
    quickSort(arr, middle + 1, end);
}

分区算法实现

快速排序中最重要的便是分区算法,也就是 partition 函数。
partition 函数需要做的事情就是将 arr 从 start 到 end 分区,左边区域比基数小,右边区域比基数大,然后返回中间值的下标。那么首先我们要做的事情就是选择一个基数,基数我们一般称之为 pivot,意为“轴”。整个数组就像围绕这个轴进行旋转,小于轴的数字旋转到左边,大于轴的数字旋转到右边。

基数的选择
基数的选择没有固定标准,随意选择区间内任何一个数字做基数都可以。通常来讲有三种选择方式:

  • 选择第一个元素作为基数
  • 选择最后一个元素作为基数
  • 选择区间内一个随机元素作为基数

选择的基数不同,算法的实现也不同。实际上第三种选择方式的平均时间复杂度是最优的。
最简单的思路是:从 left 开始,遇到比基数大的数,就交换到数组最后,并将 right 减一,直到 left 和 right 相遇,此时数组就被分成了左右两个区域。再将基数和中间的数交换,返回中间值的下标即可。

public static void quickSort(int[] arr) {
    quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int start, int end) {
    // 如果区域内的数字少于 2 个,退出递归
    if (start >= end) return;
    // 将数组分区,并获得中间值的下标
    int middle = partition(arr, start, end);
    // 对左边区域快速排序
    quickSort(arr, start, middle - 1);
    // 对右边区域快速排序
    quickSort(arr, middle + 1, end);
}
// 将 arr 从 start 到 end 分区,左边区域比基数小,右边区域比基数大,然后返回中间值的下标
public static int partition(int[] arr, int start, int end) {
    // 取第一个数为基数
    int pivot = arr[start];
    // 从第二个数开始分区
    int left = start + 1;
    // 右边界
    int right = end;
    // left、right 相遇时退出循环
    while (left < right) {
        // 找到第一个大于基数的位置
        while (left < right && arr[left] <= pivot) left++;
        // 交换这两个数,使得左边分区都小于或等于基数,右边分区大于或等于基数
        if (left != right) {
            exchange(arr, left, right);
            right--;
        }
    }
    // 如果 left 和 right 相等,单独比较 arr[right] 和 pivot
    if (left == right && arr[right] > pivot) right--;
    // 将基数和中间数交换
    if (right != start) exchange(arr, start, right);
    // 返回中间值的下标
    return right;
}
private static void exchange(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}




Java 已经将洗牌算法封装到了集合类中,即 Collections.shuffle() 函数。洗牌算法由 Ronald A.Fisher 和 Frank Yates 于 1938 年发明,思路是每次从未处理的数据中随机取出一个数字,然后把该数字放在数组中所有未处理数据的尾部。

以上是关于快速排序算法(LeetCode版)的主要内容,如果未能解决你的问题,请参考以下文章

5 行代码实现快速排序(简易版)

LeetCode排序专题算法

LeetCode排序专题算法

2.排序算法实现(JavaScript版)-冒泡-选择-快速排序

基于比较的七种常见排序算法

基于比较的七种常见排序算法