快速排序(QuickSort)也是一种排序算法,对包含n个数组的输入数组,最坏情况运行时间为O(n^2)。虽然这个最坏情况运行时间比较差,但是快速排序通常是用于排序的最佳实用选择,这是因为其平均性能相当好,期望的运行时间为O(nlgn),且O(nlgn)中隐含的常数因子很小,另外它还能够进行就地排序在虚拟环境中也能很好的工作。

GitHub chapter 7 程序代码下载

原理

快速排序也和合并排序一样,基于分治法,分为分解、解决、合并三个步骤;

分解:数组array[low…high]被分为两个(可能空)子数组array[low…temp-1]和array[temp+1…high],使得array[low…temp-1]中的每一个元素都小于等于array[temp],而array[temp+1…high]中的每一个元素都大于array[temp],下标temp也是在这个过程中被计算出来;

解决:通过递归的调用快速排序,对子数组array[low…temp-1],array[temp+1…high]进行排序;

合并:因为两个子数组是就地排序的,将他们的合并不需要操作,整个数组array[low…high]是已经排好序的。

本章介绍了快速排序算法的原理、程序实现(包括随机化版本)及其性能分析。

快排算法实现

  1. #include <iostream>
  2. #include <ctime>
  3. #include <cstdlib>
  4. #define N 10
  5. using namespace std;
  6. //快速排序的递归算法
  7. void quickSort(int * array, int low, int high);
  8. //求分割点
  9. int partition(int * array, int low, int high);
  10. //交换两个变量的值
  11. void exchange(int &a, int &b);
  12. int main()
  13. {
  14. //声明一个待排序数组
  15. int array[N];
  16. //设置随机化种子,避免每次产生相同的随机数
  17. srand(time(0));
  18. for (int i = 0; i<N; i++)
  19. {
  20. array[i] = rand() % 101;//数组赋值使用随机函数产生1-100之间的随机数
  21. }
  22. cout << "排序前:" << endl;
  23. for (int j = 0; j<N; j++)
  24. {
  25. cout << array[j] << " ";
  26. }
  27. cout << endl << "排序后:" << endl;
  28. //调用快速排序函数对该数组进行排序
  29. quickSort(array, 0, N - 1);
  30. for (int k = 0; k<N; k++)
  31. {
  32. cout << array[k] << " ";
  33. }
  34. cout << endl;
  35. return 0;
  36. }//main
  37. void quickSort(int * array, int low, int high)
  38. {
  39. if (low < high)
  40. {
  41. int temp = partition(array, low, high);
  42. quickSort(array, low, temp - 1);
  43. quickSort(array, temp + 1, high);
  44. }
  45. }
  46. int partition(int * array, int low, int high)
  47. {
  48. int i = low - 1;
  49. //默认将划分段的最后一个元素为主元
  50. int x = array[high];
  51. for (int j = low; j<high; j++)
  52. {
  53. if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数,右边均是大于它的数
  54. {
  55. i += 1;
  56. exchange(array[i], array[j]);
  57. }
  58. }
  59. exchange(array[i + 1], array[high]);
  60. return i + 1;//所以循环完毕后,i+1就是该数组的分割点
  61. }
  62. void exchange(int &a, int &b)
  63. {
  64. int temp = a;
  65. a = b;
  66. b = temp;
  67. }

快速排序的随机化版本

在上面介绍的快速排序算法实现中,Partition(A , p , r)总是默认A[r]为主元,作为比较标准。如果可以采用随机取样的随机化技术的话,将会使得分析更加简单。下面是随机化版本的快速排序算法实现:

  1. #include <iostream>
  2. #include <ctime>
  3. #include <cstdlib>
  4. #define N 10
  5. using namespace std;
  6. //快速排序的递归算法
  7. void quickSort(int * array, int low, int high);
  8. //求分割点
  9. int partition(int * array, int low, int high);
  10. //以low ~ high 之间的一个随机元素作为主元 , 求分割点
  11. int randomPartition(int *array, int low, int high);
  12. //交换两个变量的值
  13. void exchange(int &a, int &b);
  14. int main()
  15. {
  16. //声明一个待排序数组
  17. int array[N];
  18. //设置随机化种子,避免每次产生相同的随机数
  19. srand(time(0));
  20. for (int i = 0; i<N; i++)
  21. {
  22. array[i] = rand() % 101;//数组赋值使用随机函数产生1-100之间的随机数
  23. }
  24. cout << "排序前:" << endl;
  25. for (int j = 0; j<N; j++)
  26. {
  27. cout << array[j] << " ";
  28. }
  29. cout << endl << "排序后:" << endl;
  30. //调用快速排序函数对该数组进行排序
  31. quickSort(array, 0, N - 1);
  32. for (int k = 0; k<N; k++)
  33. {
  34. cout << array[k] << " ";
  35. }
  36. cout << endl;
  37. system("pause");
  38. return 0;
  39. }//main
  40. void quickSort(int * array, int low, int high)
  41. {
  42. if (low < high)
  43. {
  44. int temp = randomPartition(array, low, high);
  45. quickSort(array, low, temp - 1);
  46. quickSort(array, temp + 1, high);
  47. }
  48. }
  49. int partition(int * array, int low, int high)
  50. {
  51. int i = low - 1;
  52. //默认将划分段的最后一个元素为主元
  53. int x = array[high];
  54. for (int j = low; j<high; j++)
  55. {
  56. if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数,右边均是大于它的数
  57. {
  58. i += 1;
  59. exchange(array[i], array[j]);
  60. }
  61. }
  62. exchange(array[i + 1], array[high]);
  63. return i + 1;//所以循环完毕后,i+1就是该数组的分割点
  64. }
  65. int randomPartition(int *array, int low, int high)
  66. {
  67. //找到low ~ high 之间的一个随机位置
  68. int i = rand() % (high - low + 1) + low;
  69. //交换该随机主元至尾部,
  70. exchange(array[i], array[high]);
  71. return partition(array, low, high);
  72. }
  73. void exchange(int &a, int &b)
  74. {
  75. int temp = a;
  76. a = b;
  77. b = temp;
  78. }

随机版本的快排与普通快排区别并不是很大,改动的仅仅是求分割点步骤中的主元选取,也就是增加了randomPartition函数,选定好主元元素下标i后,将该元素交换至段尾,依然调用partition函数求分割点。

快速排序性能分析

快速排序的运行时间与划分是否对称有关,而后者又与选择了哪一个元素进行划分有关。如果划分是对称的,那么本算法在渐近意义上与合并排序一样快,如果划分是不对称的那么本算法在渐进意义上与插入排序一样慢。下面分别讨论快速排序的最坏情况划分、最佳情况划分、平衡的划分。

最坏情况划分:快速排序的最坏情况划分行为发生在划分过程中产生的两个区域分别包含n-1个元素和0个元素的时候。假设算法每次递归调用都出现了这种不对称划分,划分的时间代价为O(n),因为对一个大小为0的数组进行递归调用后,返回了T(n)=O(1),故算法的运行时间可递归的表示为:

T(n) = T(n-1) + T(0) + O(n) = T(n-1) + O(n)

从直观上来看,如果将每一层递归的代价加起来,就可以得到一个算术级数(等式(array,2)其和值的量极为O(n^2))利用代换法可以比较直接的证明递归式 T(n) = T(n-1) + O(n)的解为 T(n) = O(n^2)。

因此如果在算法的每一层递归上,划分都是最大程度不对称的,那么算法的运行时间为O(n^2),亦即快速排序算法的最坏情况运行时间不如插入排序的好。此外当输入数组完全排好序时,快速排序的运行时间是O(n^2),而插入排序的运行时间为O(n)。

最佳情况划分:在Partition可能做的最平衡划分中,得到的两个子问题的大小都不可能大于[n/2],因为若其中一个子问题的大小为[n/2],则另外一个子问题的大小必然为[n/2]-1。在这种情况下,快速排序的运行速度要快得多,这时表达其运行时间的递归式为:

T(n) <= 2T(n/2) + O(n)

解该递归式可得T(n) = O(nlgn)。由于在每一层递归划分的两边都是对称的,因此从渐进意义上来看,算法运行的就更快了。

平衡的划分: 快速排序的平均情况运行时间与其最佳情况运行时间很接近,而不是非常接近与其最坏情况运行时间(证明原因详细参考《算法导论》原书第二版P88),因为任何一种按常数比例进行划分都会产生深度为O(lgn)的递归树,其中每一层的代价都是O(n),因而每当按照常数比例进行划分时,总的运行时间都是O(nlgn)。

《算法导论》 — Chapter 7 快速排序的更多相关文章

  1. 基于visual Studio2013解决算法导论之009快速排序随机版本

     题目 快速排序随机版本 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <malloc.h> ...

  2. 基于visual Studio2013解决算法导论之008快速排序算法

     题目 快速排序 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <malloc.h> #in ...

  3. (搬运)《算法导论》习题解答 Chapter 22.1-1(入度和出度)

    (搬运)<算法导论>习题解答 Chapter 22.1-1(入度和出度) 思路:遍历邻接列表即可; 伪代码: for u 属于 Vertex for v属于 Adj[u] outdegre ...

  4. 《算法导论》 — Chapter 7 高速排序

    序 高速排序(QuickSort)也是一种排序算法,对包括n个数组的输入数组.最坏情况执行时间为O(n^2). 尽管这个最坏情况执行时间比較差.可是高速排序一般是用于排序的最佳有用选择.这是由于其平均 ...

  5. 《算法导论》— Chapter 9 中位数和顺序统计学

    序 在算法导论的第二部分主要探讨了排序和顺序统计学,第六章~第八章讨论了堆排序.快速排序以及三种线性排序算法.该部分的最后一个章节,将讨论顺序统计方面的知识. 在一个由n个元素组成的集合中,第i个顺序 ...

  6. 《算法导论》 — Chapter 8 线性时间排序

    序 到目前为止,关于排序的问题,前面已经介绍了很多,从插入排序.合并排序.堆排序以及快速排序,每一种都有其适用的情况,在时间和空间复杂度上各有优势.它们都有一个相同的特点,以上所有排序的结果序列,各个 ...

  7. 《算法导论》— Chapter 15 动态规划

    序 算法导论一书的第四部分-高级设计和分析技术从本章开始讨论,主要分析高效算法的三种重要技术:动态规划.贪心算法以及平摊分析三种. 首先,本章讨论动态规划,它是通过组合子问题的解而解决整个问题的,通常 ...

  8. 《算法导论》— Chapter 11 散列表

    1 序 在很多应用中,都要用到一种动态集合结构,它仅支持INSERT.SEARCH以及DELETE三种字典操作.例如计算机程序设计语言的编译程序需要维护一个符号表,其中元素的关键字为任意字符串,与语言 ...

  9. [置顶] 《算法导论》习题解答搬运&&学习笔记 索引目录

    开始学习<算法导论>了,虽然是本大部头,可能很难一下子看完,我还是会慢慢地研究的. 课后的习题和思考有些是很有挑战性的题目,我等蒻菜很难独立解决. 然后发现了Google上有挺全的algo ...

随机推荐

  1. Zernike矩之图像重建(附源码)

    源码下载 参考: [1] Teague M R. Image analysis via the general theory of moments[J]. JOSA, 1980, 70(8): 920 ...

  2. Android中ProgressBar显示小数的方法

    Android原生的ProgressBar的ProgressDialog.STYLE_HORIZONTAL(即水平样式)默认setMax和setProgress只能传int型的参数,而实际项目中我需要 ...

  3. Backbone学习记录(4)

    事件绑定  on()方法 调用格式:object.on(event, callback, [context])"change" — 当attributes变化时"chan ...

  4. Super Mario 树状数组离线 || 线段树

    Super Mario Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  5. python中函数参数

    默认参数注意点 优点:灵活,当没有指定与形参对应的实参时就会使用默认参数 缺陷: 例子: >>> def h(m, l=[]):                    #默认参数时列 ...

  6. Execution of 'source /usr/hdp/current/oozie-server/conf/oozie--env.sh: oozie admin -oozie http://nssa-sensor3:11000/oozie -status' returned 255.解决办法(图文详解)

    不多说,直接上干货! 问题详情 解决办法 Copy/Paste oozie.services property tag set from oozie-default.xml to oozie-site ...

  7. Smart 组件 vs Dumb 组件

    大家已经知道,只会接受 props 并且渲染确定结果的组件我们把它叫做 Dumb 组件,这种组件只关心一件事情 —— 根据 props 进行渲染. Dumb 组件最好不要依赖除了 React.js 和 ...

  8. ios app跳转拨打电话界面,调用拨打电话功能

    DNLogFUNC //两种方法都可以用 //这种据说是可以上appstore NSURL *phoneURL = [NSURL URLWithString:[NSString stringWithF ...

  9. JSP 错误处理方法

    web.xml中配置error-page标签 1.WEB工程中打开 web.xml 文件

  10. docker上配置nginx负载均衡

    采用ubuntu系统,docker安装自行百度 1.安装tomcat docker run -d -p : tomcat docker run -d -p : tomcat 安装两个实例,端口分别为8 ...