快速排序算法深度解析:分治思想的经典实现

快速排序是由英国计算机科学家托尼·霍尔在1960年提出的排序算法,它采用分治法策略,在平均情况下具有O(n log n)的时间复杂度,是实际应用中最常用的排序算法之一。

1. 算法原理

1.1 基本思想

快速排序采用”分治法”策略:

  1. 选择基准元素:从数组中选择一个元素作为”基准”(pivot)
  2. 分区操作:重新排列数组,使小于基准的元素都在基准左侧,大于基准的元素都在右侧
  3. 递归排序:递归地对基准左侧和右侧的子数组进行快速排序

1.2 算法步骤详解

1
2
3
4
5
6
7
8
9
10
11
12
原始数组: [3, 6, 8, 10, 1, 2, 1]
选择基准: 3

分区过程:
[3, 6, 8, 10, 1, 2, 1] → 选择3作为基准
[1, 2, 1, 3, 6, 8, 10] → 分区后,3左侧都≤3,右侧都>3

递归处理:
左子数组: [1, 2, 1] → [1, 1, 2]
右子数组: [6, 8, 10] → [6, 8, 10]

最终结果: [1, 1, 2, 3, 6, 8, 10]

2. C语言实现

2.1 基础版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>

// 交换两个元素
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}

// Lomuto分区方案
int partition_lomuto(int arr[], int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为基准
int i = low - 1; // 小于基准的元素的索引

printf(" 分区操作: 基准=%d, 范围[%d,%d]\n", pivot, low, high);
printf(" 分区前: ");
for (int k = low; k <= high; k++) {
printf("%d ", arr[k]);
}
printf("\n");

for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}

swap(&arr[i + 1], &arr[high]);

printf(" 分区后: ");
for (int k = low; k <= high; k++) {
if (k == i + 1) {
printf("[%d] ", arr[k]); // 基准位置
} else {
printf("%d ", arr[k]);
}
}
printf("\n");

return i + 1;
}

// 快速排序主函数
void quick_sort_basic(int arr[], int low, int high, int depth) {
if (low < high) {
printf("第%d层递归: 排序范围[%d,%d]\n", depth, low, high);

// 分区操作
int pi = partition_lomuto(arr, low, high);

// 递归排序左右子数组
printf("递归排序左子数组[%d,%d]\n", low, pi - 1);
quick_sort_basic(arr, low, pi - 1, depth + 1);

printf("递归排序右子数组[%d,%d]\n", pi + 1, high);
quick_sort_basic(arr, pi + 1, high, depth + 1);
}
}

// 测试基础快速排序
void test_basic_quick_sort() {
printf("=== 基础快速排序测试 ===\n");

int arr[] = {3, 6, 8, 10, 1, 2, 1};
int n = sizeof(arr) / sizeof(arr[0]);

printf("原始数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");

quick_sort_basic(arr, 0, n - 1, 0);

printf("\n最终结果: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");
}

2.2 Hoare分区方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Hoare分区方案(原始方案)
int partition_hoare(int arr[], int low, int high) {
int pivot = arr[low]; // 选择第一个元素作为基准
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]);
}
}

// 使用Hoare分区的快速排序
void quick_sort_hoare(int arr[], int low, int high) {
if (low < high) {
int pi = partition_hoare(arr, low, high);
quick_sort_hoare(arr, low, pi);
quick_sort_hoare(arr, pi + 1, high);
}
}

// 三路快速排序(处理大量重复元素)
void quick_sort_3way(int arr[], int low, int high) {
if (low >= high) return;

int lt = low; // arr[low..lt-1] < pivot
int gt = high; // arr[gt+1..high] > pivot
int i = low + 1; // arr[lt..i-1] == pivot
int pivot = arr[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++;
}
}

// 现在 arr[low..lt-1] < pivot = arr[lt..gt] < arr[gt+1..high]
quick_sort_3way(arr, low, lt - 1);
quick_sort_3way(arr, gt + 1, high);
}

// 测试不同分区方案
void test_partition_schemes() {
printf("=== 不同分区方案测试 ===\n");

int arr1[] = {3, 6, 8, 10, 1, 2, 1};
int arr2[] = {3, 6, 8, 10, 1, 2, 1};
int arr3[] = {5, 5, 5, 3, 5, 5, 7, 5};
int n1 = sizeof(arr1) / sizeof(arr1[0]);
int n2 = sizeof(arr2) / sizeof(arr2[0]);
int n3 = sizeof(arr3) / sizeof(arr3[0]);

printf("Hoare分区方案:\n");
printf("排序前: ");
for (int i = 0; i < n1; i++) printf("%d ", arr1[i]);
printf("\n");

quick_sort_hoare(arr1, 0, n1 - 1);

printf("排序后: ");
for (int i = 0; i < n1; i++) printf("%d ", arr1[i]);
printf("\n\n");

printf("三路快速排序(适合重复元素):\n");
printf("排序前: ");
for (int i = 0; i < n3; i++) printf("%d ", arr3[i]);
printf("\n");

quick_sort_3way(arr3, 0, n3 - 1);

printf("排序后: ");
for (int i = 0; i < n3; i++) printf("%d ", arr3[i]);
printf("\n\n");
}

2.3 优化版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// 随机化基准选择
int partition_randomized(int arr[], int low, int high) {
// 随机选择基准并交换到末尾
int random_index = low + rand() % (high - low + 1);
swap(&arr[random_index], &arr[high]);

return partition_lomuto(arr, low, high);
}

// 三数取中选择基准
int median_of_three(int arr[], int low, int high) {
int mid = low + (high - low) / 2;

if (arr[mid] < arr[low]) {
swap(&arr[low], &arr[mid]);
}
if (arr[high] < arr[low]) {
swap(&arr[low], &arr[high]);
}
if (arr[high] < arr[mid]) {
swap(&arr[mid], &arr[high]);
}

// 将中位数放到末尾作为基准
swap(&arr[mid], &arr[high]);
return partition_lomuto(arr, low, high);
}

// 混合排序:小数组使用插入排序
void insertion_sort_range(int arr[], int low, int high) {
for (int i = low + 1; i <= high; i++) {
int key = arr[i];
int j = i - 1;

while (j >= low && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}

arr[j + 1] = key;
}
}

// 优化的快速排序
void quick_sort_optimized(int arr[], int low, int high) {
if (low < high) {
// 小数组使用插入排序
if (high - low < 10) {
insertion_sort_range(arr, low, high);
return;
}

// 使用三数取中选择基准
int pi = median_of_three(arr, low, high);

quick_sort_optimized(arr, low, pi - 1);
quick_sort_optimized(arr, pi + 1, high);
}
}

// 尾递归优化
void quick_sort_tail_recursive(int arr[], int low, int high) {
while (low < high) {
// 小数组优化
if (high - low < 10) {
insertion_sort_range(arr, low, high);
return;
}

int pi = median_of_three(arr, low, high);

// 始终先处理较小的子数组,然后用尾递归处理较大的
if (pi - low < high - pi) {
quick_sort_tail_recursive(arr, low, pi - 1);
low = pi + 1;
} else {
quick_sort_tail_recursive(arr, pi + 1, high);
high = pi - 1;
}
}
}

// 测试优化版本
void test_optimized_quick_sort() {
printf("=== 优化版本测试 ===\n");

const int size = 20;
int original[size];

// 生成测试数据
srand(time(NULL));
for (int i = 0; i < size; i++) {
original[i] = rand() % 50;
}

int arr1[size], arr2[size], arr3[size];
memcpy(arr1, original, sizeof(original));
memcpy(arr2, original, sizeof(original));
memcpy(arr3, original, sizeof(original));

printf("原始数组: ");
for (int i = 0; i < size; i++) printf("%d ", original[i]);
printf("\n\n");

printf("标准快速排序结果: ");
quick_sort_basic(arr1, 0, size - 1, 0);
for (int i = 0; i < size; i++) printf("%d ", arr1[i]);
printf("\n");

printf("优化快速排序结果: ");
quick_sort_optimized(arr2, 0, size - 1);
for (int i = 0; i < size; i++) printf("%d ", arr2[i]);
printf("\n");

printf("尾递归优化结果: ");
quick_sort_tail_recursive(arr3, 0, size - 1);
for (int i = 0; i < size; i++) printf("%d ", arr3[i]);
printf("\n\n");
}

2.4 迭代版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 栈结构用于迭代实现
typedef struct {
int low, high;
} StackFrame;

typedef struct {
StackFrame* data;
int top;
int capacity;
} Stack;

Stack* create_stack(int capacity) {
Stack* stack = (Stack*)malloc(sizeof(Stack));
stack->data = (StackFrame*)malloc(capacity * sizeof(StackFrame));
stack->top = -1;
stack->capacity = capacity;
return stack;
}

bool is_empty(Stack* stack) {
return stack->top == -1;
}

void push(Stack* stack, int low, int high) {
if (stack->top < stack->capacity - 1) {
stack->top++;
stack->data[stack->top].low = low;
stack->data[stack->top].high = high;
}
}

StackFrame pop(Stack* stack) {
StackFrame frame = {-1, -1};
if (!is_empty(stack)) {
frame = stack->data[stack->top];
stack->top--;
}
return frame;
}

// 迭代版本的快速排序
void quick_sort_iterative(int arr[], int low, int high) {
Stack* stack = create_stack(high - low + 1);

push(stack, low, high);

while (!is_empty(stack)) {
StackFrame frame = pop(stack);
int l = frame.low;
int h = frame.high;

if (l < h) {
int pi = partition_lomuto(arr, l, h);

// 将子问题压入栈
push(stack, l, pi - 1);
push(stack, pi + 1, h);
}
}

free(stack->data);
free(stack);
}

// 测试迭代版本
void test_iterative_quick_sort() {
printf("=== 迭代版本测试 ===\n");

int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);

printf("排序前: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");

quick_sort_iterative(arr, 0, n - 1);

printf("排序后: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n\n");
}

3. 算法分析

3.1 时间复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 性能统计结构
typedef struct {
int comparisons;
int swaps;
int recursive_calls;
double time_taken;
} QuickSortStats;

QuickSortStats stats;

// 带统计的分区函数
int partition_with_stats(int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;

for (int j = low; j < high; j++) {
stats.comparisons++;
if (arr[j] <= pivot) {
i++;
if (i != j) {
swap(&arr[i], &arr[j]);
stats.swaps++;
}
}
}

if (i + 1 != high) {
swap(&arr[i + 1], &arr[high]);
stats.swaps++;
}

return i + 1;
}

// 带统计的快速排序
void quick_sort_with_stats(int arr[], int low, int high) {
if (low < high) {
stats.recursive_calls++;
int pi = partition_with_stats(arr, low, high);
quick_sort_with_stats(arr, low, pi - 1);
quick_sort_with_stats(arr, pi + 1, high);
}
}

// 时间复杂度分析
void analyze_time_complexity() {
printf("=== 时间复杂度分析 ===\n");

printf("快速排序的时间复杂度:\n");
printf("- 最好情况: O(n log n) - 每次分区都平分数组\n");
printf("- 平均情况: O(n log n) - 随机分布的数据\n");
printf("- 最坏情况: O(n²) - 每次选择的基准都是最值\n\n");

int sizes[] = {100, 200, 400};
printf("实际测试验证:\n");
printf("%-10s %-15s %-15s %-15s %-10s\n", "数组大小", "最好情况", "平均情况", "最坏情况", "递归次数");
printf("-----------------------------------------------------------------------\n");

for (int s = 0; s < 3; s++) {
int size = sizes[s];

// 最好情况:基本有序
int* best_case = (int*)malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
best_case[i] = i;
}

// 最坏情况:逆序
int* worst_case = (int*)malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
worst_case[i] = size - i;
}

// 平均情况:随机
int* average_case = (int*)malloc(size * sizeof(int));
srand(42);
for (int i = 0; i < size; i++) {
average_case[i] = rand() % size;
}

// 测试最好情况
memset(&stats, 0, sizeof(stats));
clock_t start = clock();
quick_sort_with_stats(best_case, 0, size - 1);
clock_t end = clock();
int best_ops = stats.comparisons + stats.swaps;
int best_calls = stats.recursive_calls;

// 测试最坏情况
memset(&stats, 0, sizeof(stats));
quick_sort_with_stats(worst_case, 0, size - 1);
int worst_ops = stats.comparisons + stats.swaps;
int worst_calls = stats.recursive_calls;

// 测试平均情况
memset(&stats, 0, sizeof(stats));
quick_sort_with_stats(average_case, 0, size - 1);
int avg_ops = stats.comparisons + stats.swaps;
int avg_calls = stats.recursive_calls;

printf("%-10d %-15d %-15d %-15d %-10d\n",
size, best_ops, avg_ops, worst_ops, avg_calls);

free(best_case);
free(worst_case);
free(average_case);
}
printf("\n");
}

3.2 空间复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void analyze_space_complexity() {
printf("=== 空间复杂度分析 ===\n");

printf("快速排序的空间复杂度:\n");
printf("- 最好情况: O(log n) - 平衡的递归树\n");
printf("- 平均情况: O(log n) - 期望递归深度\n");
printf("- 最坏情况: O(n) - 退化为链式递归\n\n");

printf("空间使用分析:\n");
printf("- 递归调用栈: 主要的空间开销\n");
printf("- 局部变量: 每层递归的常数空间\n");
printf("- 原地排序: 不需要额外的数组空间\n\n");

printf("优化策略:\n");
printf("- 尾递归优化: 减少栈深度\n");
printf("- 迭代实现: 用显式栈替代递归\n");
printf("- 小数组优化: 避免深度递归\n\n");
}

4. 优缺点与应用场景

4.1 优点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void quick_sort_advantages() {
printf("=== 快速排序的优点 ===\n");

printf("1. 平均性能优秀\n");
printf(" - 平均时间复杂度O(n log n)\n");
printf(" - 常数因子小,实际运行速度快\n\n");

printf("2. 原地排序\n");
printf(" - 空间复杂度O(log n)\n");
printf(" - 不需要额外的大数组\n\n");

printf("3. 缓存友好\n");
printf(" - 局部性好,访问模式规律\n");
printf(" - CPU缓存利用率高\n\n");

printf("4. 易于并行化\n");
printf(" - 子问题相互独立\n");
printf(" - 适合多线程实现\n\n");

printf("5. 实用性强\n");
printf(" - 许多编程语言的标准排序算法\n");
printf(" - 工程实践中的首选\n\n");
}

4.2 缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void quick_sort_disadvantages() {
printf("=== 快速排序的缺点 ===\n");

printf("1. 最坏情况性能差\n");
printf(" - 最坏时间复杂度O(n²)\n");
printf(" - 对已排序数据表现不佳\n\n");

printf("2. 不稳定排序\n");
printf(" - 可能改变相等元素的顺序\n");
printf(" - 不适合稳定性要求的场景\n\n");

printf("3. 递归实现的栈溢出风险\n");
printf(" - 最坏情况递归深度O(n)\n");
printf(" - 可能导致栈溢出\n\n");

printf("4. 基准选择敏感\n");
printf(" - 基准选择影响性能\n");
printf(" - 需要额外的优化策略\n\n");
}

4.3 适用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void quick_sort_use_cases() {
printf("=== 适用场景 ===\n");

printf("1. 大数据集排序\n");
printf(" - 数据量大且内存有限\n");
printf(" - 对平均性能要求高\n\n");

printf("2. 系统级排序\n");
printf(" - 操作系统内核排序\n");
printf(" - 标准库实现\n\n");

printf("3. 实时系统\n");
printf(" - 平均性能可预测\n");
printf(" - 内存使用可控\n\n");

printf("4. 并行计算\n");
printf(" - 多核CPU环境\n");
printf(" - 分布式排序系统\n\n");

printf("不适用场景:\n");
printf("- 需要稳定排序的场合\n");
printf("- 对最坏情况敏感的系统\n");
printf("- 小数据集(开销大于收益)\n\n");
}

5. 主函数和测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int main() {
printf("========================================\n");
printf(" 快速排序算法完整演示\n");
printf("========================================\n\n");

// 基础功能测试
test_basic_quick_sort();

// 不同分区方案测试
test_partition_schemes();

// 优化版本测试
test_optimized_quick_sort();

// 迭代版本测试
test_iterative_quick_sort();

// 时间复杂度分析
analyze_time_complexity();

// 空间复杂度分析
analyze_space_complexity();

// 优缺点分析
quick_sort_advantages();
quick_sort_disadvantages();
quick_sort_use_cases();

printf("========================================\n");
printf(" 测试完成\n");
printf("========================================\n");

return 0;
}

6. 总结

快速排序是计算机科学中最重要的算法之一,其影响力远超排序领域:

核心特点

  • 时间复杂度: 平均O(n log n), 最坏O(n²)
  • 空间复杂度: 平均O(log n), 最坏O(n)
  • 稳定性: 不稳定
  • 实用性: 极高,广泛应用于实际系统

设计思想的价值

  1. 分治策略: 将大问题分解为小问题
  2. 原地算法: 高效利用内存空间
  3. 随机化: 通过随机化避免最坏情况
  4. 工程优化: 实际应用中的多种优化技巧

现实意义

快速排序不仅是一个排序算法,更是分治思想的经典体现,其设计理念影响了众多算法的发展。理解快速排序对于掌握算法设计的核心思想具有重要意义。

在实际应用中,现代快速排序实现通常结合了多种优化技术,如混合排序、随机化基准选择、三路分区等,使其能够在各种数据分布下都保持优秀的性能表现。

版权所有,如有侵权请联系我