深入解析C语言三路快速排序算法
快速排序(Quick Sort)是计算机科学中最经典的排序算法之一,平均时间复杂度为O(nlogn)。但当数组中存在大量重复元素时,传统快速排序效率会显著下降。本文将详细介绍一种优化版本——三路快速排序(3-Way Quick Sort),它能够高效处理重复元素,是实际应用中更优的选择。
一、传统快速排序的局限性
传统快速排序采用二路划分:
选择一个基准值(pivot)
将数组分为两部分:小于等于基准值的元素和大于基准值的元素
c
// 传统二路快排分区函数 int partition(int* arr, int low, int high) {int pivot = arr[high];int i = low;for (int j = low; j < high; j++) {if (arr[j] <= pivot) {swap(&arr[i], &arr[j]);i++;}}swap(&arr[i], &arr[high]);return i; }
问题:当数组包含大量重复元素时,会导致分区不平衡,递归深度增加,性能退化到O(n²)。
二、三路快速排序原理
三路快速排序由Dijkstra提出,核心思想是将数组划分为三部分:
小于基准值的元素
等于基准值的元素
大于基准值的元素
这种划分方式能有效处理重复元素,减少不必要的递归调用。
三、算法实现
c
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp; }void threeWayQuickSort(int* arr, int low, int high) {if (low >= high) return;// 初始化指针int lt = low; // 小于区的右边界int gt = high; // 大于区的左边界int i = low + 1; // 当前元素指针int pivot = arr[low]; // 选择第一个元素作为基准值while (i <= gt) {if (arr[i] < pivot) {swap(&arr[lt], &arr[i]);lt++;i++;}else if (arr[i] > pivot) {swap(&arr[i], &arr[gt]);gt--;// 注意:这里不增加i,因为交换来的新元素需要重新检查}else {i++;}}// 递归排序小于区和大于区threeWayQuickSort(arr, low, lt - 1);threeWayQuickSort(arr, gt + 1, high); }
四、算法步骤详解
初始化指针:
lt
(less than)指向小于区的末尾gt
(greater than)指向大于区的开头i
是当前遍历指针
元素分类处理:
当前元素 < 基准值:交换到lt位置,lt和i都右移
当前元素 > 基准值:交换到gt位置,gt左移
当前元素 = 基准值:直接跳过(i右移)
终止条件:当i超过gt时结束
递归排序:只对小于区和大于区进行递归,等于区已有序
五、示例演示
以数组 [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
为例:
第一次分区(pivot=3):
小于区:[1, 1, 2]
等于区:[3, 3]
大于区:[4, 5, 9, 6, 5]
递归处理小于区和大于区
最终结果:[1, 1, 2, 3, 3, 4, 5, 5, 6, 9]
六、性能分析
时间复杂度:
最佳/平均情况:O(nlogn)
最坏情况:O(n²)(但概率极低)
含大量重复元素时接近O(n)
空间复杂度:
递归栈空间:O(logn)
与传统快排对比:
重复元素越多,优势越明显
普通快排可能退化为O(n²),三路快排仍保持高效
七、优化技巧
随机化基准值:
c
// 在分区前添加: int random = low + rand() % (high - low + 1); swap(&arr[low], &arr[random]); pivot = arr[low];
小数组改用插入排序:
c
if (high - low + 1 <= 16) {insertionSort(arr, low, high);return; }
三数取中法选择基准值
八、应用场景
需要处理大量重复元素的数据集
需要稳定排序的场合
作为系统排序算法的优化版本