代码功能总结
这段代码实现了随机化快速排序算法,并通过以下步骤验证其功能:
- 随机数生成 :使用
srand(time(0))
初始化随机数种子,生成 100 个范围在 0~999 之间的随机整数。 - 排序过程 :
- 基准选择:每次递归随机选择一个元素作为基准(pivot),减少最坏情况发生的概率。
- 分区操作 :通过
partition
函数将数组分为两部分,左侧元素≤基准,右侧元素≥基准。 - 递归排序:对左右两部分分别递归调用快速排序。
- 性能评估 :使用
clock()
函数测量排序耗时,并输出结果。 - 优化处理:当数组长度超过 50 时,自动省略原始数据和排序结果的完整打印,避免输出过多信息。
核心算法分析
1. 随机化基准选择
cpp
int random = low + rand() % (high - low + 1);
swap(&arr[random], &arr[high]);
- 作用:通过随机选择基准元素,避免在已排序或接近排序的数据上出现最坏情况(时间复杂度从 O (n²) 降至 O (n log n))。
2. 分区函数(Partition)
cpp
int pivot = arr[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]);
return (i + 1);
- 原理:将基准元素放到正确位置,使左侧元素≤基准,右侧元素≥基准。
- 复杂度:时间复杂度 O (n),空间复杂度 O (1)。
3. 递归排序
cpp
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
- 分治思想:不断将数组划分为更小的子数组,直到子数组长度为 1 或 0。
性能分析
- 时间复杂度 :
- 平均情况:O (n log n)(随机化基准有效避免最坏情况)。
- 最坏情况:O (n²)(理论上仍可能发生,但概率极低)。
- 空间复杂度:O (log n)(递归调用栈的深度)。
- 稳定性:不稳定(元素交换可能改变相同值元素的相对顺序)。
代码优点
- 随机化优化:通过随机选择基准,显著提高了算法在各种数据分布下的性能。
- 计时功能 :使用
clock()
函数直观展示排序耗时,便于性能对比。 - 用户体验优化:自动判断数组长度,避免打印过长数据。
- 模块化设计:函数职责清晰(交换、分区、排序、打印),便于维护和复用。
潜在改进点
-
小规模数据优化:
- 当子数组长度较小时(如≤16),使用插入排序(时间复杂度 O (n²),但常数项更小)可能更快。
-
三路划分(Three-Way Partitioning):
- 对于包含大量重复元素的数据,使用三路划分将数组分为
<
、=
、>
三部分,可减少递归次数。
- 对于包含大量重复元素的数据,使用三路划分将数组分为
-
尾递归优化:
- 对较大的子数组先进行递归,较小的子数组使用循环,降低栈空间消耗。
-
三数取中法(Median of Three):
- 选择首、中、尾三个元素的中间值作为基准,进一步提高划分平衡性。
适用场景
- 大规模数据排序:快速排序的平均性能优于冒泡、选择、插入等简单排序算法。
- 内存受限环境:原地排序(in-place),空间复杂度低。
- 随机分布数据:随机化基准确保算法在大多数情况下表现良好。
总结
这段代码实现了一个功能完整、性能优良的随机化快速排序算法,通过随机基准选择有效避免了最坏情况。代码结构清晰,包含必要的输入生成、排序过程和结果验证,适合作为教学示例或基础排序工具。对于特定场景(如大量重复元素或小规模数据),可通过进一步优化提升性能。
完整测试代码
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define SIZE 100
#define INSERTION_SORT_THRESHOLD 16
// 交换两个元素的函数
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 插入排序(处理小规模数据)
void insertionSort(int arr[], int low, int high) {
for (int i = low + 1; i <= high; i++) {
int key = arr[i];
int j = i - 1;
// 将比key大的元素后移
while (j >= low && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
// 三数取中法选择基准
int medianOfThree(int arr[], int low, int high) {
int mid = low + (high - low) / 2;
// 保证arr[low] <= arr[mid] <= arr[high]
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]);
// 将中间值交换到high-1位置
swap(&arr[mid], &arr[high-1]);
return arr[high-1]; // 返回基准值
}
// 三路划分处理重复元素
void quickSort3Way(int arr[], int low, int high) {
if (low >= high) return;
// 小规模数据使用插入排序
if (high - low + 1 <= INSERTION_SORT_THRESHOLD) {
insertionSort(arr, low, high);
return;
}
// 三数取中法选择基准
int pivot = medianOfThree(arr, low, high);
// 三路划分:[low, lt-1] < pivot, [lt, gt] == pivot, [gt+1, high] > pivot
int lt = low; // 小于区域的右边界
int gt = high; // 大于区域的左边界
int i = 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--;
} else { // arr[i] == pivot
i++;
}
}
// 尾递归优化:只递归处理较小的部分
if (low < lt - 1)
quickSort3Way(arr, low, lt - 1);
if (gt + 1 < high)
quickSort3Way(arr, gt + 1, high);
}
// 打印数组函数
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
// 主函数
int main()
{
int arr[SIZE];
// 设置随机数种子(基于当前时间)
srand(time(0));
// 生成100个随机数(范围:0~999)
for (int i = 0; i < SIZE; i++) {
arr[i] = rand() % 1000; // 0~999
}
int n = sizeof(arr) / sizeof(arr[0]);
printf("原数组: \n");
if (n <= 50) { // 当数组不太长时才打印,避免刷屏
printArray(arr, n);
} else {
printf("数组过大,省略打印...\n");
}
// 计时开始
clock_t start, end;
double cpu_time_used;
start = clock();
// 使用优化后的三路快速排序
quickSort3Way(arr, 0, n - 1);
// 计时结束
end = clock();
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("排序后的数组: \n");
if (n <= 50) { // 当数组不太长时才打印,避免刷屏
printArray(arr, n);
} else {
printf("数组过大,省略打印...\n");
}
printf("排序耗时: %f 秒\n", cpu_time_used);
return 0;
}