跳转至

HB1-快速排序

✅ 一、基础版快速排序(递归实现)

C
// 分区函数:选择第一个元素为基准,使用形参作为指针
int partition(int arr[], int low, int high) {
    int pivot = arr[low];  // 选择第一个元素作为基准

    while (low < high) {
        // 从右向左找第一个小于基准的元素
        while (low < high && arr[high] >= pivot) {
            high--;
        }
        arr[low] = arr[high];  // 将小元素移到左边

        // 从左向右找第一个大于基准的元素
        while (low < high && arr[low] <= pivot) {
            low++;
        }
        arr[high] = arr[low];  // 将大元素移到右边
    }

    arr[low] = pivot;  // 将基准元素放到正确位置
    return low;        // 返回基准元素的最终位置
}

// 快速排序主函数
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);  // 分区操作

        quickSort(arr, low, pi - 1);         // 递归排序左半部分
        quickSort(arr, pi + 1, high);        // 递归排序右半部分
    }
}

✅ 二、优化版:三路快排(处理重复元素)

当数组中存在大量重复元素时,标准快排性能下降(退化为 O(n²)),三路快排可有效优化。

C
// 三路快排:将数组分为 <pivot, ==pivot, >pivot 三部分
// 返回等于pivot区间的左右边界 [left, right]
void threeWayQuickSort(int arr[], int low, int high) {
    if (low >= high) return;

    int pivot = arr[low];
    int lt = low;      // arr[low...lt-1] < pivot
    int gt = high;     // arr[gt+1...high] > pivot
    int i = low + 1;   // 当前考察位置

    while (i <= gt) {
        if (arr[i] < pivot) {
            swap(&arr[i++], &arr[lt++]);
        } else if (arr[i] > pivot) {
            swap(&arr[i], &arr[gt--]);
        } else {
            i++;
        }
    }

    // 此时 [low, lt-1] < pivot, [lt, gt] == pivot, [gt+1, high] > pivot
    threeWayQuickSort(arr, low, lt - 1);
    threeWayQuickSort(arr, gt + 1, high);
}

✅ 优点:对重复元素多的数据性能极佳,平均时间复杂度仍为 O(n log n),最坏情况更少。


✅ 三、非递归实现(使用栈模拟递归)

避免递归调用栈溢出,适用于大数组或栈空间受限环境。

C
#include <stdlib.h>

typedef struct {
    int low, high;
} StackNode;

void quickSortIterative(int arr[], int low, int high) {
    StackNode* stack = (StackNode*)malloc(sizeof(StackNode) * (high - low + 1));
    int top = -1;

    stack[++top] = (StackNode){low, high};

    while (top >= 0) {
        low = stack[top].low;
        high = stack[top--].high;

        if (low < high) {
            int pi = partition(arr, low, high);

            // 先压左区间,再压右区间(保证先处理左)
            stack[++top] = (StackNode){low, pi - 1};
            stack[++top] = (StackNode){pi + 1, high};
        }
    }

    free(stack);
}

⚠️ 注意:手动管理栈内存,需 free() 防止泄漏。


✅ 四、随机化快排(避免最坏情况)

选择随机基准,防止在已排序数组上退化为 O(n²)。

C
#include <stdlib.h>
#include <time.h>

// 随机化分区
int randomPartition(int arr[], int low, int high) {
    srand(time(NULL));  // 实际使用中应在主函数初始化一次
    int random = low + rand() % (high - low + 1);
    swap(&arr[random], &arr[high]);  // 将随机元素换到末尾
    return partition(arr, low, high);
}

void randomizedQuickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = randomPartition(arr, low, high);
        randomizedQuickSort(arr, low, pi - 1);
        randomizedQuickSort(arr, pi + 1, high);
    }
}

✅ 建议:srand(time(NULL)) 应在 main() 中调用一次,避免多次调用导致随机性差。


✅ 算法核心思路总结

步骤 说明
1. 选择基准(pivot) 通常选首、尾、中位数或随机元素
2. 分区(Partition) 将小于/等于/大于 pivot 的元素分到不同区域
3. 递归处理左右子数组 对左右两部分分别快排

核心是 分治思想:Divide and Conquer。


✅ 时间复杂度分析

情况 时间复杂度 说明
最好情况 O(n log n) 每次分区都均分数组
平均情况 O(n log n) 数学期望下性能优秀
最坏情况 O(n²) 每次选到最大或最小元素作 pivot(如已排序数组)
空间复杂度 O(log n) ~ O(n) 递归栈深度,最好 O(log n),最坏 O(n)

🔍 提示:通过 随机化 pivot三数取中法 可极大降低最坏情况概率。


✅ 性能对比与其他排序比较

排序算法 平均时间 最坏时间 空间复杂度 是否稳定 适用场景
快速排序 O(n log n) O(n²) O(log n) ❌ 不稳定 通用、内存排序首选
归并排序 O(n log n) O(n log n) O(n) ✅ 稳定 外部排序、稳定需求
堆排序 O(n log n) O(n log n) O(1) 内存受限、最坏性能要求高
插入排序 O(n²) O(n²) O(1) 小数组、近有序

💡 快排是 实际应用中最常用的内部排序算法,C标准库 qsort() 即基于快排优化实现。


✅ 相关知识拓展

1. Hoare 与 Lomuto 分区方案

  • Lomuto:本文使用,代码简洁,但效率略低。
  • Hoare:原版快排分区,使用两个指针从两端向中间扫描,效率更高,但 pivot 不一定在最终位置。
C
// Hoare 分区示例
int hoarePartition(int arr[], int low, int high) {
    int pivot = arr[low];
    int i = low - 1, j = high + 1;

    while (1) {
        do i++; while (arr[i] < pivot);
        do j--; while (arr[j] > pivot);
        if (i >= j) return j;
        swap(&arr[i], &arr[j]);
    }
}

2. 混合排序(Introsort)

现代C++ std::sort 使用 Introsort:结合快排、堆排序、插入排序。

  • 快排主导;
  • 递归深度超过阈值时切换为堆排序(防止 O(n²));
  • 小数组(如 n < 16)用插入排序。

3. 原地排序 & 不稳定

  • 快排是 原地排序(in-place),仅需少量额外空间。
  • 不稳定:相等元素可能改变相对顺序。

✅ 应用场景

场景 是否适合快排 说明
内存中排序 ✅ 强烈推荐 性能最优
外部排序(文件) 推荐归并排序
要求稳定性 改用归并或插入
实时系统(最坏性能敏感) 改用堆排序或归并
大量重复元素 ✅(用三路快排) 避免退化

✅ 变体汇总

变体 特点
随机快排 防止恶意输入导致退化
三路快排 优化重复元素场景
非递归快排 节省栈空间,防爆栈
三数取中法 pivot 选 mid = (low+high)/2,取 low, mid, high 的中位数
小数组插入排序优化 n < 10 时用插入排序提升常数性能

✅ 记忆口诀(背诵用)

一选基准,二做分区,三递归排左右。
随机 pivot 防退化,三路划分重元素。
非递归用栈模拟,小数组插排更优。


✅ 总结

快速排序是 理论与实践结合最成功的排序算法之一。虽然最坏时间复杂度为 O(n²),但通过合理优化(随机化、三路划分、混合策略),在绝大多数场景下性能远超其他 O(n log n) 算法。

掌握以下几点即可应对面试与工程:

  1. 基础递归 + Lomuto 分区(必会)
  2. 三路快排(应对重复元素)
  3. 随机化 pivot(防最坏情况)
  4. 时间复杂度分析与对比