分治范式下的快速排序全解:C++实现、时间复杂度优化与工程化实践

81 阅读4分钟

一、算法原理与分治思想

快速排序采用典型的分治策略(Divide-and-Conquer),核心操作分为三个阶段:

1.分解(Partitioning)

  • 选定基准元素(pivot)
  • 将数组划分为两个子区间:左区间的元素均 ≤ pivot,右区间的元素均 ≥ pivot

2.递归求解

  • 对左右子区间递归执行快速排序

3.合并(Implicit Merge)

  • 由于排序在分解过程中完成,无需显式合并操作
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); // 处理右区间
    }
}

二、关键操作:分区算法

1. Lomuto分区法(经典实现)

核心思想: 通过单次遍历完成元素分类

    int partition(int arr[], int low, int high) {
    int pivot = arr[high];          // 选择末尾元素作为基准
    int i = low - 1;                // 小于基准区的边界指针
    
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr[i], arr[j]);   // 将小元素交换到左区
        }
    }
    swap(arr[i + 1], arr[high]);    // 基准归位到正确位置
    return i + 1;                   // 返回基准索引
}

实例分析(数组[3,1,4,2,5]):

初始状态:i=-1, pivot=5
j=0: 3<=5 → i=0 → swap(arr[0],arr[0]) → 无变化
j=1: 1<=5 → i=1 → swap(arr[1],arr[1]) → 无变化
j=2: 4<=5 → i=2 → swap(arr[2],arr[2]) → 无变化
j=3: 2<=5 → i=3 → swap(arr[3],arr[3]) → 无变化
最终交换arr[4]与arr[4] → 基准归位

2. Hoare分区法(原始实现)

优势: 减少元素交换次数,适合存在大量重复元素的场景

int partitionHoare(int arr[], int low, int high) {
    int pivot = arr[(low + high) / 2]; // 中间元素作为基准
    int i = low - 1;
    int j = high + 1;
    
    while (true) {
        do { i++; } while (arr[i] < pivot);
        do { j--; } while (arr[j] > pivot);
        
        if (i >= j) return j;
        swap(arr[i], arr[j]);
    }
}

执行过程对比(数组[5,3,8,4,2,7,1,6]):

初始选择中间元素4为基准
i从左侧找到5>4,j从右侧找到1<4
交换51[1,3,8,4,2,7,5,6]
继续扫描直到i=2(8), j=4(2) → 交换 → [1,3,2,4,8,7,5,6]
最终i=3, j=2 → 返回j=2

三、时间复杂度分析

1.最佳情况(平衡划分)

  • 每次划分产生近似相等的子问题

  • 递归式:T(n) = 2T(n/2) + O(n) → O(n log n)

2.最差情况(完全失衡)

  • 每次划分产生空子区间(如已排序数组选择首元素为基准)

  • T(n) = T(n-1) + O(n) → O(n²)

3.平均情况

  • 数学期望证明为O(n log n)

  • 实际应用中接近最佳情况

四、关键优化策略

1.三数取中法(Median-of-Three)

int mid = low + (high - low)/2;
if (arr[low] > arr[mid]) swap(arr[low], arr[mid]);
if (arr[low] > arr[high]) swap(arr[low], arr[high]);
if (arr[mid] > arr[high]) swap(arr[mid], arr[high]);
swap(arr[mid], arr[high-1]); // 将中位数放到high-1位置
return partition(arr, low, high);

2.尾递归优化

void quickSortTail(int arr[], int low, int high) {
    while (low < high) {
        int pi = partition(arr, low, high);
        if (pi - low < high - pi) {
            quickSortTail(arr, low, pi-1);
            low = pi + 1;
        } else {
            quickSortTail(arr, pi+1, high);
            high = pi - 1;
        }
    }
}

3.混合排序策略

  • 当子数组长度≤16时切换为插入排序
void hybridSort(int arr[], int low, int high) {
    if (high - low > 16) {
        int pi = partition(arr, low, high);
        hybridSort(arr, low, pi-1);
        hybridSort(arr, pi+1, high);
    } else {
        insertionSort(arr, low, high);
    }
}

五、稳定性与空间复杂度

1.不稳定性示例:

  • 原始数组:[(5,a), (3,b), (5,c), (2,d)]

  • 排序过程可能变为:[2,d, 3,b, 5,c, 5,a]

2.空间复杂度:

  • 最佳递归深度:O(log n)

  • 最差递归深度:O(n)

六、完整C++实现

#include <iostream>
#include <algorithm>
using namespace std;
 
int medianOfThree(int arr[], int low, int high) {
    int mid = low + (high - low)/2;
    if (arr[low] > arr[mid]) swap(arr[low], arr[mid]);
    if (arr[low] > arr[high]) swap(arr[low], arr[high]);
    if (arr[mid] > arr[high]) swap(arr[mid], arr[high]);
    swap(arr[mid], arr[high-1]);
    return arr[high-1];
}
 
int optimizedPartition(int arr[], int low, int high) {
    int pivot = medianOfThree(arr, low, high);
    int i = low - 1;
    
    for (int j = low; j < high-1; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i+1], arr[high-1]);
    return i+1;
}
 
void quickSort(int arr[], int low, int high) {
    if (high - low > 16) {
        if (low < high) {
            int pi = optimizedPartition(arr, low, high);
            quickSort(arr, low, pi-1);
            quickSort(arr, pi+1, high);
        }
    } else {
        // 插入排序实现略
    }
}
 
// 测试用例
int main() {
    int data[] = {9,7,5,11,12,2,14,3,10,6};
    int n = sizeof(data)/sizeof(data[0]);
    
    quickSort(data, 0, n-1);
    
    for (int i=0; i<n; i++)
        cout << data[i] << " ";
    return 0;
}

七、算法扩展:三路快速排序

处理含大量重复元素的优化方案:

void quickSort3Way(int arr[], int low, int high) {
    if (high <= low) return;
    
    int lt = low, gt = high;
    int pivot = arr[low];
    int i = low;
    
    while (i <= gt) {
        if (arr[i] < pivot) {
            swap(arr[lt++], arr[i++]);
        } else if (arr[i] > pivot) {
            swap(arr[i], arr[gt--]);
        } else {
            i++;
        }
    }
    quickSort3Way(arr, low, lt-1);
    quickSort3Way(arr, gt+1, high);
}

该实现将数组划分为三部分:

  • 小于pivot(< pivot)

  • 等于pivot(== pivot)

  • 大于pivot(> pivot)

八、工程实践建议

1.在标准库实现中(如C++ STL的std::sort),通常结合:

  • 快速排序作为主算法

  • 堆排序作为递归深度保护(IntroSort)

  • 插入排序处理小规模数据

2.避免递归过深的措施:

  • 监控递归深度

  • 超过2log₂n层后切换堆排序

3.内存优化方向:

  • 使用迭代代替递归

  • 优先处理较小分区