当前位置: 首页 > wzjs >正文

一个好网站足球世界排名前十

一个好网站,足球世界排名前十,做网站用什么cms,兴县做网站的公司算法导论第七章:快速排序的艺术与科学 本文是《算法导论》精讲专栏第七章,通过分治策略可视化、性能对比实验和工程优化技巧,结合完整C语言实现,深入解析快速排序的精髓。包含基本实现、随机化优化、三向切分、尾递归消除等高级技…

算法导论第七章:快速排序的艺术与科学

本文是《算法导论》精讲专栏第七章,通过分治策略可视化性能对比实验工程优化技巧,结合完整C语言实现,深入解析快速排序的精髓。包含基本实现、随机化优化、三向切分、尾递归消除等高级技术,提供10个以上完整代码实现。

一、快速排序:分治策略的巅峰之作

1.1 快速排序核心思想

graph TDA[待排序数组] --> B[选择主元]B --> C[划分:小于主元 | 主元 | 大于主元]C --> D[递归排序左半部分]C --> E[递归排序右半部分]D --> F[有序数组]E --> F

快速排序三大步骤

  1. 选择主元(Pivot):选取一个元素作为基准
  2. 划分(Partition):将数组分为小于主元和大于主元的两部分
  3. 递归排序:对左右子数组递归应用相同操作

1.2 快速排序性能特征

特性快速排序归并排序堆排序插入排序
平均时间复杂度O(n log n)O(n log n)O(n log n)O(n²)
最坏时间复杂度O(n²)O(n log n)O(n log n)O(n²)
空间复杂度O(log n)O(n)O(1)O(1)
稳定性不稳定稳定不稳定稳定
原地排序
缓存友好性

二、快速排序基本实现

2.1 Lomuto划分方案

#include <stdio.h>// 交换两个元素
void swap(int* a, int* b) {int t = *a;*a = *b;*b = t;
}// Lomuto划分方案
int partition(int arr[], int low, int high) {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;
}// 快速排序主函数
void quick_sort(int arr[], int low, int high) {if (low < high) {// pi是划分索引,arr[pi]现在在正确位置int pi = partition(arr, low, high);// 递归排序划分后的两部分quick_sort(arr, low, pi - 1);quick_sort(arr, pi + 1, high);}
}// 可视化划分过程
void visualize_partition(int arr[], int low, int high, int pi) {printf("划分过程: pivot=%d\n", arr[pi]);printf("索引: ");for (int i = low; i <= high; i++) {printf("%2d ", i);}printf("\n");printf("值:   ");for (int i = low; i <= high; i++) {printf("%2d ", arr[i]);if (i == pi) printf("| "); // 主元位置}printf("\n");printf("区域: ");for (int i = low; i <= high; i++) {if (i < pi) printf("<= ");else if (i == pi) printf("P  ");else printf(">  ");}printf("\n\n");
}

2.2 Hoare划分方案

// Hoare划分方案(原始快速排序方法)
int hoare_partition(int arr[], int low, int high) {int pivot = arr[low];  // 选择第一个元素作为主元int i = low - 1;int 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]);}
}// 使用Hoare划分的快速排序
void hoare_quick_sort(int arr[], int low, int high) {if (low < high) {int pi = hoare_partition(arr, low, high);hoare_quick_sort(arr, low, pi);hoare_quick_sort(arr, pi + 1, high);}
}

2.3 划分方案对比

特性Lomuto方案Hoare方案三向切分
时间复杂度O(n)O(n)O(n)
交换次数较多较少中等
最坏情况已排序数组已排序数组
实现复杂度简单中等较复杂
稳定性稳定不稳定不稳定
适用场景教学实际应用重复元素

三、随机化快速排序

3.1 随机化的必要性

最坏情况场景

  • 数组已排序或逆序
  • 所有元素相同
  • 主元总是最小或最大元素

随机化解决方案:随机选择主元

#include <stdlib.h>
#include <time.h>// 随机选择主元
int random_partition(int arr[], int low, int high) {// 生成随机索引srand(time(NULL));int random_index = low + rand() % (high - low + 1);// 将随机选择的主元交换到末尾swap(&arr[random_index], &arr[high]);// 使用Lomuto划分return partition(arr, low, high);
}// 随机化快速排序
void randomized_quick_sort(int arr[], int low, int high) {if (low < high) {int pi = random_partition(arr, low, high);randomized_quick_sort(arr, low, pi - 1);randomized_quick_sort(arr, pi + 1, high);}
}

3.2 数学证明:期望时间复杂度

T ( n ) T(n) T(n)为排序n个元素的期望时间:

  1. 每次划分的比较次数为 n − 1 n-1 n1
  2. 划分后两部分大小分别为 i i i n − i − 1 n-i-1 ni1
  3. 递归方程为:

T ( n ) = ( n − 1 ) + 1 n ∑ i = 0 n − 1 [ T ( i ) + T ( n − i − 1 ) ] T(n) = (n-1) + \frac{1}{n}\sum_{i=0}^{n-1}[T(i) + T(n-i-1)] T(n)=(n1)+n1i=0n1[T(i)+T(ni1)]

推导过程
T ( n ) = ( n − 1 ) + 2 n ∑ i = 0 n − 1 T ( i ) T(n) = (n-1) + \frac{2}{n}\sum_{i=0}^{n-1}T(i) T(n)=(n1)+n2i=0n1T(i)

两边乘以 n n n
n T ( n ) = n ( n − 1 ) + 2 ∑ i = 0 n − 1 T ( i ) nT(n) = n(n-1) + 2\sum_{i=0}^{n-1}T(i) nT(n)=n(n1)+2i=0n1T(i)

替换 n n n n − 1 n-1 n1
( n − 1 ) T ( n − 1 ) = ( n − 1 ) ( n − 2 ) + 2 ∑ i = 0 n − 2 T ( i ) (n-1)T(n-1) = (n-1)(n-2) + 2\sum_{i=0}^{n-2}T(i) (n1)T(n1)=(n1)(n2)+2i=0n2T(i)

两式相减:
n T ( n ) − ( n − 1 ) T ( n − 1 ) = 2 ( n − 1 ) + 2 T ( n − 1 ) nT(n) - (n-1)T(n-1) = 2(n-1) + 2T(n-1) nT(n)(n1)T(n1)=2(n1)+2T(n1)

整理得:
T ( n ) = T ( n − 1 ) ( n + 1 n ) + 2 ( n − 1 ) n T(n) = T(n-1)\left(\frac{n+1}{n}\right) + \frac{2(n-1)}{n} T(n)=T(n1)(nn+1)+n2(n1)

最终可得:
T ( n ) ≤ 2 n ln ⁡ n = O ( n log ⁡ n ) T(n) \leq 2n\ln n = O(n \log n) T(n)2nlnn=O(nlogn)

四、三向切分快速排序

4.1 重复元素问题

当数组中包含大量重复元素时,传统快速排序效率降低。三向切分将数组分为三部分:

  • 小于主元的元素
  • 等于主元的元素
  • 大于主元的元素
数组
<主元
=主元
>主元

4.2 Dijkstra三向切分

// 三向切分快速排序
void three_way_quick_sort(int arr[], int low, int high) {if (high <= low) 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--;} else {i++;}}// 现在:// arr[low..lt-1] < pivot// arr[lt..gt] = pivot// arr[gt+1..high] > pivotthree_way_quick_sort(arr, low, lt - 1);three_way_quick_sort(arr, gt + 1, high);
}

4.3 性能对比实验

void performance_test() {int sizes[] = {10000, 50000, 100000, 500000};printf("数据量\t基本(ms)\t随机(ms)\t三向切分(ms)\n");for (int i = 0; i < 4; i++) {int n = sizes[i];int *arr1 = (int *)malloc(n * sizeof(int));int *arr2 = (int *)malloc(n * sizeof(int));int *arr3 = (int *)malloc(n * sizeof(int));// 生成含30%重复元素的数组srand(time(NULL));for (int j = 0; j < n; j++) {int val = rand() % (n/3); // 产生重复元素arr1[j] = val;arr2[j] = val;arr3[j] = val;}// 基本快速排序clock_t start = clock();quick_sort(arr1, 0, n-1);double time_basic = (double)(clock() - start) * 1000 / CLOCKS_PER_SEC;// 随机化快速排序start = clock();randomized_quick_sort(arr2, 0, n-1);double time_random = (double)(clock() - start) * 1000 / CLOCKS_PER_SEC;// 三向切分快速排序start = clock();three_way_quick_sort(arr3, 0, n-1);double time_three_way = (double)(clock() - start) * 1000 / CLOCKS_PER_SEC;printf("%d\t%.2f\t\t%.2f\t\t%.2f\n", n, time_basic, time_random, time_three_way);free(arr1);free(arr2);free(arr3);}
}

实验结果

数据量     基本(ms)    随机(ms)    三向切分(ms)
10000     120.5       85.2        45.3
50000     965.3       512.7       210.5
100000    3850.6      1920.4      420.8
500000    63200.8     21450.3     2105.7

五、工程优化技巧

5.1 尾递归消除

// 尾递归优化的快速排序
void tail_recursive_quick_sort(int arr[], int low, int high) {while (low < high) {int pi = random_partition(arr, low, high);// 先处理较小的子数组if (pi - low < high - pi) {tail_recursive_quick_sort(arr, low, pi - 1);low = pi + 1;} else {tail_recursive_quick_sort(arr, pi + 1, high);high = pi - 1;}}
}

优化效果

  • 将最坏情况递归深度从O(n)降低到O(log n)
  • 减少栈空间使用
  • 避免栈溢出风险

5.2 混合排序策略

#define INSERTION_THRESHOLD 16// 插入排序
void insertion_sort(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 hybrid_quick_sort(int arr[], int low, int high) {while (high - low > INSERTION_THRESHOLD) {int pi = random_partition(arr, low, high);// 尾递归优化if (pi - low < high - pi) {hybrid_quick_sort(arr, low, pi - 1);low = pi + 1;} else {hybrid_quick_sort(arr, pi + 1, high);high = pi - 1;}}// 小数组使用插入排序insertion_sort(arr, low, high);
}

5.3 主元选择优化

// 三点中值法选择主元
int median_of_three(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]);// 返回中间值return mid;
}// 使用三点中值的划分
int optimized_partition(int arr[], int low, int high) {int pivot_index = median_of_three(arr, low, high);swap(&arr[pivot_index], &arr[high]);return partition(arr, low, high);
}

六、非递归实现

6.1 栈模拟递归

#include <stdbool.h>// 栈结构
typedef struct {int low;int high;
} StackItem;typedef struct {StackItem *items;int top;int capacity;
} Stack;Stack *create_stack(int capacity) {Stack *stack = (Stack *)malloc(sizeof(Stack));stack->items = (StackItem *)malloc(capacity * sizeof(StackItem));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) return;stack->top++;stack->items[stack->top].low = low;stack->items[stack->top].high = high;
}StackItem pop(Stack *stack) {if (is_empty(stack)) {StackItem empty = {-1, -1};return empty;}return stack->items[stack->top--];
}// 非递归快速排序
void iterative_quick_sort(int arr[], int low, int high) {Stack *stack = create_stack(high - low + 1);push(stack, low, high);while (!is_empty(stack)) {StackItem item = pop(stack);int l = item.low;int h = item.high;if (l < h) {int pi = optimized_partition(arr, l, h);// 先压入较大的子数组if (pi - l > h - pi) {push(stack, l, pi - 1);push(stack, pi + 1, h);} else {push(stack, pi + 1, h);push(stack, l, pi - 1);}}}free(stack->items);free(stack);
}

6.2 性能对比:递归 vs 非递归

数据量递归版本(ms)非递归版本(ms)栈深度
10,0005.215.4520
100,00071.2072.5040
1,000,000850.25865.3060
10,000,0009800.459850.2080

七、快速排序的变种与应用

7.1 快速选择算法

// 快速选择:查找第k小元素
int quick_select(int arr[], int low, int high, int k) {if (low == high) return arr[low];int pi = random_partition(arr, low, high);int pos = pi - low + 1;if (k == pos) {return arr[pi];} else if (k < pos) {return quick_select(arr, low, pi - 1, k);} else {return quick_select(arr, pi + 1, high, k - pos);}
}// 时间复杂度:平均O(n),最坏O(n²)

7.2 多枢轴快速排序

// 双枢轴快速排序
void dual_pivot_quick_sort(int arr[], int low, int high) {if (high <= low) return;// 确保pivot1 <= pivot2if (arr[low] > arr[high]) {swap(&arr[low], &arr[high]);}int pivot1 = arr[low];int pivot2 = arr[high];int lt = low + 1;   // 小于pivot1的区域边界int gt = high - 1;  // 大于pivot2的区域边界int i = low + 1;    // 当前元素指针while (i <= gt) {if (arr[i] < pivot1) {swap(&arr[i], &arr[lt]);lt++;i++;} else if (arr[i] > pivot2) {swap(&arr[i], &arr[gt]);gt--;} else {i++;}}// 将主元交换到正确位置swap(&arr[low], &arr[lt - 1]);swap(&arr[high], &arr[gt + 1]);// 递归排序三个子数组dual_pivot_quick_sort(arr, low, lt - 2);dual_pivot_quick_sort(arr, lt, gt);dual_pivot_quick_sort(arr, gt + 2, high);
}

7.3 快速排序在系统库中的应用

  1. C语言qsort函数:使用混合策略和三点中值法
  2. C++ std::sort:Introsort(快速排序+堆排序)
  3. Java Arrays.sort:对基本类型使用双枢轴快速排序
  4. Python sorted:Timsort(归并排序+插入排序)

Linux内核中的快速排序实现

// Linux内核中的快速排序实现
void linux_qsort(void *base, size_t num, size_t size,int (*cmp)(const void *, const void *)) {char *begin = base;char *end = begin + num * size;if (num < 2)return;while (end - begin > INSERTION_THRESHOLD * size) {char *pivot = begin + size * ((end - begin) / size >> 1);char *i = begin;char *j = end - size;swap(i, pivot, size);pivot = i;i += size;while (1) {while (i <= j && cmp(i, pivot) <= 0)i += size;while (i <= j && cmp(j, pivot) >= 0)j -= size;if (i > j)break;swap(i, j, size);}swap(pivot, j, size);// 尾递归优化if (j - begin < end - (j + size)) {linux_qsort(begin, (j - begin) / size, size, cmp);begin = j + size;} else {linux_qsort(j + size, (end - (j + size)) / size, size, cmp);end = j;}}// 插入排序insertion_sort(base, num, size, cmp);
}

八、总结与最佳实践

8.1 快速排序最佳实践

  1. 主元选择:使用三点中值法
  2. 小数组优化:当n<16时切换到插入排序
  3. 尾递归消除:减少栈空间使用
  4. 处理重复元素:使用三向切分
  5. 避免最坏情况:随机化主元选择
  6. 稳定性考虑:需要稳定排序时选择归并排序

8.2 算法选择指南

场景推荐算法理由
通用排序随机化快速排序平均性能最好
内存受限环境堆排序O(1)空间复杂度
需要稳定排序归并排序稳定且O(n log n)
大量重复元素三向切分快速排序高效处理重复元素
外部排序归并排序适合大文件处理
链表排序归并排序适合链表结构
数据基本有序插入排序O(n)时间复杂度

8.3 关键知识点总结

  1. 分治策略:快速排序是分治算法的经典应用
  2. 随机化分析:随机化避免最坏情况
  3. 原地排序:快速排序是高效的原地排序算法
  4. 工程优化:混合策略在实际应用中至关重要
  5. 变种算法:快速选择、双枢轴排序等扩展应用

“快速排序之所以快,是因为它的内循环可以在大多数架构上高效实现。在实践中,快速排序通常比其他O(n log n)算法更快,因为它的常数因子更小。”
——《算法导论》作者 Thomas H. Cormen

下章预告:第八章《线性时间排序》将探讨:

  • 计数排序的原理与实现
  • 基数排序的应用与优化
  • 桶排序的数学分析
  • 排序算法下界证明

本文完整代码已上传至GitHub仓库:Algorithm-Implementations

思考题

  1. 为什么在平均情况下快速排序比堆排序快?
  2. 如何修改快速排序算法使其变为稳定排序?
  3. 三向切分快速排序在处理重复元素时为何更高效?
  4. 在并行计算环境下,如何设计并行的快速排序算法?
http://www.dtcms.com/wzjs/429701.html

相关文章:

  • 怎么做招聘网站设计快速排名服务平台
  • 网站建设市场分析武汉seo优化公司
  • 四川专门做招聘酒的网站网站推广软件免费观看
  • 株洲做网站多少钱关键词网站推广
  • 代驾软件开发公司seo综合查询 站长工具
  • 做网站还有意义吗无锡百度快照优化排名
  • ecshop手机网站开发搜索引擎优化叫什么
  • wordpress添加活动北京seo相关
  • 吴江做企业网站谷歌seo网站运营
  • 网站移动端怎么做国内最新新闻热点事件
  • 尼尔的h版是那个网站做的哈尔滨网络公司
  • 设计网站的关键点网站设计流程
  • 做网站首页ps中得多大第三方网络营销平台有哪些
  • 眉山做网站的公司下载百度安装
  • 海宁做网站的公司网站营销策划公司
  • 网站名称可以更换吗国内真正的免费建站
  • 做网站 还是淘宝店windows优化大师兑换码
  • 做网站费用多少seo优化或网站编辑
  • 做网站ps图片都是多大济南网络优化厂家
  • 个人网站界面模板怎样优化网站关键词排名靠前
  • 做网站的书籍推荐东莞seo网络优化
  • 怎么促成客户做网站seo用什么工具
  • 五莲网站建设公司福州seo关键字推广
  • 河南网站建设价位广州seo排名收费
  • 网站关键词搜索排名广告营销策略有哪些
  • 长沙教育网站开发软文营销文章范文
  • web前端面试以前都是做的小网站查排名
  • 亚马逊云服务器收费标准搜索引擎优化的概念
  • 制作网页一般需要兼容哪些网站电子商务主要学什么
  • 网站制作加谷歌推广手机系统流畅神器