排序算法完全指南(五):快速排序深度详解

引言

前面我们学习了冒泡、选择、插入、基数四种排序。今天要讲的快速排序 ,是二十世纪十大算法之一,也是实际工程中最常用的排序算法。C 语言的 qsort、C++ 的 std::sort、Java 的 Arrays.sort,底层都是快速排序或其变体。

快速排序的核心是分治思想:选一个基准值,把数组分成"比基准小"和"比基准大"两半,然后递归处理两半。这种"大事化小"的策略,让它的平均时间复杂度达到了 O(n log n),而且常数因子比归并排序和堆排序都小。

第一部分:算法思想

一、核心原理

快速排序的三步:

  1. 选基准:从数组中选一个元素作为基准值(pivot)

  2. 划分:把小于基准的放左边,大于基准的放右边

  3. 递归:对左右两部分分别重复这个过程

二、挖坑法划分过程

挖坑法口诀:左边挖坑右边填,右边挖坑左边填,左右相遇放基准。


第二部分:代码实现

一、挖坑法划分函数

cpp 复制代码
int Partition(int arr[], int left, int right) {
    int tmp = arr[left];  // 基准值,左边留下第一个坑

    while (left != right) {
        // 从右向左找比基准小的,填左坑
        while (left < right && arr[right] > tmp) {
            right--;
        }
        if (left == right) break;
        arr[left] = arr[right];   // 小的放左边,右边留下坑
        
        // 从左向右找比基准大的,填右坑
        while (left < right && arr[left] < tmp) {
            left++;
        }
        if (left == right) break;
        arr[right] = arr[left];   // 大的放右边,左边留下坑
    }
    
    arr[left] = tmp;  // 基准值归位
    return left;       // 返回基准位置
}

二、递归版本

cpp 复制代码
void Quick_Recursion(int arr[], int left, int right) {
    // 区间不合法或只有一个元素 → 终止
    if (left >= right) return;

    // 划分,得到基准位置
    int par = Partition(arr, left, right);

    // 递归处理左右两部分
    Quick_Recursion(arr, left, par - 1);
    Quick_Recursion(arr, par + 1, right);
}

void Quick_Sort(int arr[], int len) {
    Quick_Recursion(arr, 0, len - 1);
}

三、非递归版本(用栈模拟递归)

cpp 复制代码
#include <stack>

void Quick_Sort_No_Recursion(int arr[], int len) {
    std::stack<int> s;
    s.push(0);           // 先压左边界
    s.push(len - 1);     // 再压右边界

    while (!s.empty()) {
        int right = s.top(); s.pop();  // 先取右边界
        int left  = s.top(); s.pop();  // 再取左边界

        int par = Partition(arr, left, right);

        // 左半部分还有多个元素 → 压栈
        if (left < par - 1) {
            s.push(left);
            s.push(par - 1);
        }

        // 右半部分还有多个元素 → 压栈
        if (right > par + 1) {
            s.push(par + 1);
            s.push(right);
        }
    }
}

递归 vs 非递归

版本 优点 缺点
递归 代码简洁 深度过大可能栈溢出
非递归 栈在堆上,不易溢出 代码稍长

第三部分:完整测试代码

cpp 复制代码
#include <stdio.h>
#include <stack>

int Partition(int arr[], int left, int right) {
    int tmp = arr[left];
    while (left != right) {
        while (left < right && arr[right] > tmp) right--;
        if (left == right) break;
        arr[left] = arr[right];
        
        while (left < right && arr[left] < tmp) left++;
        if (left == right) break;
        arr[right] = arr[left];
    }
    arr[left] = tmp;
    return left;
}

void Quick_Recursion(int arr[], int left, int right) {
    if (left >= right) return;
    int par = Partition(arr, left, right);
    Quick_Recursion(arr, left, par - 1);
    Quick_Recursion(arr, par + 1, right);
}

void Quick_Sort(int arr[], int len) {
    Quick_Recursion(arr, 0, len - 1);
}

void printArray(int arr[], int len, const char* msg) {
    printf("%s", msg);
    for (int i = 0; i < len; i++) printf("%d ", arr[i]);
    printf("\n");
}

int main() {
    int arr1[] = {5, 3, 8, 1, 2, 7, 6, 4};
    int len1 = sizeof(arr1) / sizeof(arr1[0]);
    printArray(arr1, len1, "排序前:");
    Quick_Sort(arr1, len1);
    printArray(arr1, len1, "排序后:");

    int arr2[] = {1, 2, 3, 4, 5, 6, 7, 8};
    int len2 = sizeof(arr2) / sizeof(arr2[0]);
    printf("\n已有序测试:\n");
    printArray(arr2, len2, "排序前:");
    Quick_Sort(arr2, len2);
    printArray(arr2, len2, "排序后:");

    int arr3[] = {8, 7, 6, 5, 4, 3, 2, 1};
    int len3 = sizeof(arr3) / sizeof(arr3[0]);
    printf("\n逆序测试:\n");
    printArray(arr3, len3, "排序前:");
    Quick_Sort(arr3, len3);
    printArray(arr3, len3, "排序后:");

    return 0;
}

第四部分:算法分析

一、时间复杂度

情况 时间复杂度 条件
最好 O(n log n) 每次划分均匀,基准恰好是中位数
最坏 O(n²) 每次基准是最大/最小值(如已有序数组取左端为基准)
平均 O(n log n) 随机数据

最坏情况演示

二、空间复杂度

版本 空间 说明
递归 O(log n) 递归栈深度
非递归 O(log n) 显式栈

三、稳定性

快速排序是不稳定的。因为划分时的跳跃式填坑会破坏相等元素的相对顺序。


第五部分:优化策略

一、三数取中法(避免最坏情况)

cpp 复制代码
// 取 left、mid、right 三者的中间值作为基准
int GetMidIndex(int arr[], int left, int right) {
    int mid = left + (right - left) / 2;
    
    if (arr[left] < arr[mid]) {
        if (arr[mid] < arr[right]) return mid;
        else if (arr[left] < arr[right]) return right;
        else return left;
    } else {
        if (arr[left] < arr[right]) return left;
        else if (arr[mid] < arr[right]) return right;
        else return mid;
    }
}

int Partition_Optimized(int arr[], int left, int right) {
    // 三数取中,交换到 left 位置
    int mid = GetMidIndex(arr, left, right);
    int tmp = arr[left];
    arr[left] = arr[mid];
    arr[mid] = tmp;
    
    // 后面和普通 Partition 一样
    tmp = arr[left];
    while (left != right) {
        while (left < right && arr[right] > tmp) right--;
        if (left == right) break;
        arr[left] = arr[right];
        while (left < right && arr[left] < tmp) left++;
        if (left == right) break;
        arr[right] = arr[left];
    }
    arr[left] = tmp;
    return left;
}

二、小数据量切换插入排序

当区间小于一定阈值(如 16)时,插入排序比快速排序更快:

cpp 复制代码
void Quick_Recursion_Optimized(int arr[], int left, int right) {
    if (left >= right) return;
    
    // 小数据量直接用插入排序
    if (right - left + 1 <= 16) {
        // 插入排序...
        return;
    }
    
    int par = Partition_Optimized(arr, left, right);
    Quick_Recursion_Optimized(arr, left, par - 1);
    Quick_Recursion_Optimized(arr, par + 1, right);
}

第六部分:与各排序算法对比

排序算法 平均 最坏 空间 稳定
冒泡排序 O(n²) O(n²) O(1)
选择排序 O(n²) O(n²) O(1)
插入排序 O(n²) O(n²) O(1)
基数排序 O(d×n) O(d×n) O(n+r)
快速排序 O(n log n) O(n²) O(log n)
希尔排序 O(n^1.3) O(n²) O(1)
归并排序 O(n log n) O(n log n) O(n)
堆排序 O(n log n) O(n log n) O(1)

总结

一、核心要点

要点 内容
算法思想 分治:选基准 → 划分 → 递归
时间复杂度 最好/平均 O(n log n),最坏 O(n²)
空间复杂度 O(log n)(递归栈)
稳定性 ❌ 不稳定
核心优化 三数取中避最坏,小数据切插入

二、代码框架记忆

三、一句话记忆

快速排序选基准,小的放左大的放右,递归处理两边。平均 O(n log n) 且常数因子最小,是最快的通用排序算法。但最坏会退化到 O(n²),需要用三数取中优化。

相关推荐
Eloudy9 小时前
定常约束的情况下,质点矢径 r_i 不显含时间t
人工智能·算法·机器学习
徐安安ye10 小时前
FlashAttention的OOM排查:为什么显存够了还是报内存不足?
人工智能·算法·机器学习
2601_9578822410 小时前
一条视频如何自动适配5大平台的技术实现
人工智能·算法·机器学习
子琦啊10 小时前
【算法复习】哈希|Map 与 Set 两个高频套路
算法·哈希算法
Alaso_shuang10 小时前
视觉组通识
数码相机·算法·计算机视觉
纽扣66710 小时前
【算法进阶之路】链表终极进阶:合并 K 个有序链表 + 复制带随机指针的链表(含双解法)
数据结构·算法·链表
迈巴赫车主11 小时前
码蹄集 MC0457符咒封印java
java·数据结构·算法
摇滚侠11 小时前
Java 零基础全套教程,数据结构与集合源码,笔记 168-174
java·数据结构·笔记
南境十里·墨染春水11 小时前
数据结构——队列
数据结构