【排序算法】快速排序详解--附详细流程代码

快速排序算法

介绍

快速排序(Quick Sort)是一种高效的分治排序算法,由英国计算机科学家 Tony Hoare 于 1960 年提出。它是实际应用中最常用的排序算法之一。快速排序的基本思想是:选择一个"基准"(pivot)元素,通过一次排序将待排序列分割成独立的两部分,一部分所有元素均小于基准,另一部分所有元素均大于基准,然后递归地对这两部分分别进行快速排序。分治策略的运用让快速排序在平均情况下能达到 O(nlogn) 的时间复杂度,大大优于简单排序算法的 O(n²) 性能。

算法步骤

  1. 从数列中选择一个元素作为"基准"(pivot),本文采用最左侧元素作为基准
  2. 将所有比基准值小的元素放到基准前面,所有比基准值大的元素放到基准后面(分区操作)
  3. 对基准左右两个子序列分别重复步骤1和2,直到子序列只有一个元素或为空

核心特性

  • 分治策略:将问题分解为更小的子问题,逐步解决
  • 原地排序:只需要 O(logn) 的额外空间复杂度(主要用于递归调用的栈空间)
  • 时间复杂度:平均情况为 O(nlogn),最坏情况为 O(n²),最好情况为 O(nlogn)
  • 不稳定性:相等元素的相对位置在排序后可能会改变
  • 高效性:在实际应用中,快速排序通常是最快的排序算法之一

基础实现

接下来大家一起看下快速排序的部分主流语言实现:

代码实现

Java实现

复制代码
public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {

            int pivotIndex = partition(arr, low, high);


            quickSort(arr, low, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    private static int partition(int[] arr, int low, int high) {

        int pivot = arr[low];
        int i = low + 1;

        for (int j = low + 1; j <= high; j++) {

            if (arr[j] < pivot) {

                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
                i++;
            }
        }


        int temp = arr[low];
        arr[low] = arr[i - 1];
        arr[i - 1] = temp;

        return i - 1;
    }


    public static void printArray(int[] arr) {
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();
    }


    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        System.out.println("排序前的数组:");
        printArray(arr);

        quickSort(arr, 0, arr.length - 1);

        System.out.println("排序后的数组:");
        printArray(arr);
    }
}

注意,在上述代码里,通过:

复制代码
for (int j = low + 1; j <= high; j++) {

    if (arr[j] < pivot) {

        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
        i++;
    }
}

将小于基准的元素移到左侧,然后通过:

复制代码
int temp = arr[low];
arr[low] = arr[i - 1];
arr[i - 1] = temp;

修正基准的位置,实现基准左侧都小于基准、基准右侧大于等于基准。

优化策略

随机选择基准元素

使用最左侧元素作为基准可能会在数组已经排序或接近排序时导致最坏性能,随机选择基准可以降低这种风险:

复制代码
private static int randomPartition(int[] arr, int low, int high) {

    int randomIndex = low + (int)(Math.random() * (high - low + 1));


    int temp = arr[randomIndex];
    arr[randomIndex] = arr[low];
    arr[low] = temp;


    return partition(arr, low, high);
}

三数取中法

选择左端、中间和右端三个元素的中值作为基准,可以进一步优化快速排序:

复制代码
private static int medianOfThreePartition(int[] arr, int low, int high) {
    int mid = low + (high - low) / 2;


    if (arr[mid] < arr[low])
        swap(arr, mid, low);
    if (arr[high] < arr[low])
        swap(arr, high, low);
    if (arr[high] < arr[mid])
        swap(arr, high, mid);


    swap(arr, mid, low);


    return partition(arr, low, high);
}

优缺点

优点

  • 平均情况下非常高效,时间复杂度为 O(nlogn)
  • 原地排序,空间复杂度低
  • 缓存友好,数据局部性好
  • 适合处理大规模数据
  • 在许多实际应用中表现优秀

缺点

  • 最坏情况下性能退化至 O(n²),比如当数组已经排序时
  • 不稳定的排序算法
  • 对于小数组,快速排序可能比其他基础排序慢
  • 递归实现可能导致栈溢出(可以使用迭代方式解决)

应用场景

  • 需要高效排序大量数据的情况
  • 作为系统库中的排序函数(如 C++ 的 std::sort、Java 的 Arrays.sort)
  • 需要就地排序且对空间复杂度敏感的场景
  • 需要平均情况下高性能的应用

扩展

双轴快速排序

Java 的 Arrays.sort() 使用的是双轴快速排序,它使用两个枢轴(基准),可以进一步提高性能:

复制代码
public static void dualPivotQuickSort(int[] arr, int low, int high) {
    if (high <= low) return;


    if (arr[low] > arr[high]) {
        int temp = arr[low];
        arr[low] = arr[high];
        arr[high] = temp;
    }


    int pivot1 = arr[low];
    int pivot2 = arr[high];

    int lt = low + 1;    
    int gt = high - 1;   
    int i = low + 1;     

    while (i <= gt) {

        if (arr[i] < pivot1) {
            int temp = arr[lt];
            arr[lt] = arr[i];
            arr[i] = temp;
            lt++;
            i++;
        } 

        else if (arr[i] > pivot2) {
            int temp = arr[i];
            arr[i] = arr[gt];
            arr[gt] = temp;
            gt--;
        } 

        else {
            i++;
        }
    }


    arr[low] = arr[lt - 1];
    arr[lt - 1] = pivot1;

    arr[high] = arr[gt + 1];
    arr[gt + 1] = pivot2;


    dualPivotQuickSort(arr, low, lt - 2);
    dualPivotQuickSort(arr, lt, gt);
    dualPivotQuickSort(arr, gt + 2, high);
}

测验

这里准备了一些测试题,方便大家判断自己的掌握情况:

  1. 快速排序的平均时间复杂度是多少?
  2. 快速排序是稳定的排序算法吗?
  3. 使用最左侧元素作为基准的快速排序在什么情况下会出现最坏性能?
  4. 快速排序的空间复杂度是多少?为什么?

测验答案

  1. O(nlogn)。
  2. 不是,因为基准元素的移动可能会改变相等元素的相对位置。
  3. 当数组已经排序或接近排序时,每次分区只能减少一个元素,时间复杂度退化为O(n²)。
  4. O(logn),主要是递归调用占用的栈空间,最坏情况下为O(n)。

相关的 LeetCode 热门题目

给大家推荐一些可以用来练手的 LeetCode 题目:

来自: 快速排序 - 算法导航

相关推荐
小羊在奋斗16 分钟前
【LeetCode 热题 100】打家劫舍 / 零钱兑换 / 单词拆分 / 乘积最大子数组 / 最长有效括号
算法·leetcode·职场和发展
JK0x072 小时前
代码随想录算法训练营 Day58 图论Ⅷ 拓扑排序 Dijkstra
android·算法·图论
oioihoii3 小时前
C++23 <spanstream>:基于 std::span 的高效字符串流处理
c++·算法·c++23
白熊1883 小时前
【机器学习基础】机器学习入门核心算法:朴素贝叶斯(Naive Bayes)
人工智能·算法·机器学习
Owen_Q3 小时前
AtCoder Beginner Contest 407
开发语言·c++·算法
客卿1234 小时前
力扣100题---字母异位词分组
算法·leetcode·职场和发展
JK0x074 小时前
代码随想录算法训练营 Day56 图论Ⅶ 最小生成树算法 Prim Kruskal
算法·图论
javthon实验室4 小时前
【Prompt Engineering】摸索出的一些小套路
算法·prompt
山楂树の4 小时前
Three.js 直线拐角自动圆角化(圆弧转弯)
算法·3d·webgl
苏荷水7 小时前
day12 leetcode-hot100-19(矩阵2)
算法·leetcode·矩阵