与归并排序一样,快速排序使用也使用了分治的思想。下面是对一个典型的子数组A[p,...,r]进行快速排序的三步分治过程:

  分解:数组A[p,...,r]被划分成两个(可能为空)子数组A[P,...,q-1]和A[q+1,...,r],使得A[p,...,q-1]中每个元素都小于等于A[q],而A[q]也小于等于A[q+1,...,r]中的每个元素。其中,计算下标q也是划分过程的一部分。

  解决:通过递归调用快速排序,对子数组啊A[P,...,q-1]和A[q+1,...,r]进行排序。

  合并:因为子数组都是原址排序的,所以不需要合并操作:数组A[P,...r]已经有序。

  下面是程序实现快速排序:

1 void QuickSort(int a[], int p, int r) {
2     if (p < r) {
3         int q = Partition(a, p, r);
4         QuickSort(A, p, q - 1);
5         QuickSort(A, q + 1, r);
6     }
7 }

  数组的划分:

  算法的关键部分是Partition过程,它实现了对子数组A[p,...r]的原址重排。快速排序的分治partition过程有两种方法。

  1) 两个下标分别从首、尾向中间扫描的方法。

  假设每次总是以当前表中第一个元素作为枢纽值(基准)对表进行划分,则必须将表中比枢纽值大的元素向右移动,比枢纽值小的元素向左移动,使得一趟Partition()操作之后,表中的元素被枢纽值一分为二。

 1 int Partition(int a[], int low, int high) {
 2     int pivot = a[low];
 3     while (low < high) {
 4         while (low < high && a[high] >= pivot) --high;
 5         a[low] = a[high];
 6         while (low < high && a[low] <= pivot) ++low;
 7         a[high] = a[low];
 8     }
 9     a[low] = pivot;
10     return low;
11 }

  2)两个指针索引一前一后逐步向后扫描的方法(算法导论)。

 1 int Partition(int a[], int low, int high) {
 2     int pivot = a[high];
 3     int i = low - 1;
 4     for (int j = low; j <= high - 1; j++) {
 5         if (a[low] <= pivot) {
 6             ++i;
 7             swap(a[i], a[j]);
 8         }
 9     }
10     swap(a[i + 1], a[high]);
11     return i+1;
12 }

  注意:上述算法有一个特点,即一次划分后,枢纽左边的相对位置不变。比如,原始序列:[3,8,7,1,2,5,6,4]->[3,1,2,4,7,5,6,8]。枢纽4左边的相对顺序不变,元素3,1,2保持在初始序列中的相对顺序(原序列中为3,...,1,2,...),某些应用要求序列的一部分保持相对顺序,这时可以考虑此种划分。

  快速排序算法的性能分析如下:

  空间效率:由于快速排序是递归的,需要借助一个递归工作栈来保存每一层递归调用的必要信息,其容量应与递归调用的最大深度一致。最好情况下为⌈log2(n+1)⌉;最坏情况下,因为要进行n-1次递归调用,所以栈的深度为O(n);平均情况下栈的深度为O(log2n)。因而空间复杂度在最坏情况下为O(n),平均情况下为O(log2n)。

  时间效率:快速排序的运行时间与划分是否对称有关,而后者又与具体使用的划分算法有关。快速排序最坏的情况发生在两个区域分别包含n-1个元素和0个元素时,这种程度的不对称性若发生在每一层递归上,即对应于初始排序表基本有序货基本逆序时,就得到最坏情况下的时间复杂度为O(n2)。

   有很多方法可以提高算法的效率。一种方法是当递归过程中划分得到的子序列的规模较小时不要再继续调用快速排序,可以直接采用直接插入排序算法进行后续的排序工作。另一种方法就是尽量选取一个可以将数据中分的枢轴元素。如从序列的头尾以及中间选取三个元素,再取这三个元素的中间值作为最终的枢轴元素(数据结构与算法分析);或者随机从当前列表中选取枢轴元素(算法导论),这样做使得最坏情况在实际安排中几乎不会发生。

  在最理想状态下,也即Partition()可能做到最平衡的划分中,得到的两个子问题的大小都不可能大于n/2,这种情况下,快速排序的运行速度将大大提升,此时,时间复杂度为O(nlog2n)。好在快速排序平均情况下运行时间与其最佳情况下的运行时间很接近,而不是接近最坏情况下的运行时间。

  快速排序是所有内部排序算法中平均性能最优的排序算法。

  稳定性:快速排序不是稳定的排序算法。

一、快速排序一次排序的应用

  1.一个数组中存储有且仅有大写和小写字母,编写一个函数对数组内的字母重新排列,让小写字母在所有大写字母之前。

 1 void Partition(char a[], int length) {
 2     if (a == NULL || length <= 0)
 3         return;
 4
 5     int i = 0;
 6     for (int j = 0; j <= length - 1; ++j) {
 7         if (a[j] >= 'a' && a[j] <= 'z') {
 8             i++;
 9             char temp = a[i];
10             a[i] = a[j];
11             a[j] = temp;
12         }
13     }
14 }

  2. 给定含有n个元素的整形数组a, 其中包括0元素和非0元素,对数组进行排序,要求:

    1)排序后所有0元素在前,所有非零元素在后,且非零元素排序前后相对位置不变。

    2)不能使用额外存储空间。、

    例如:

      输入 0、3、0、2、1、0、0

      输出 0、0、0、0、3、2、1

    解答:此处要求非零元素排序前后相对位置不变,可以利用快排一次排序的第二种情况。

void Partition(int A[], int p, int r) {int i = r + 1;for (int j = r; j >= p; --j) {if (A[j] != 0) {--i;int temp = A[i];A[i] = A[j];A[j] = temp;}}
}

  3. 荷兰国旗问题

    将乱序的红白蓝三色小球排列成同颜色在一起的小球组(按照红白蓝排序),这个问题称为荷兰国旗问题。这是因为我们可以将红白蓝小球想象成为条状物,有序排列后正好组成荷兰国旗,用0表示红球,2为篮球,1为白球。

    解答:这个问题,类似于快排中partition过程。不过,要用三个指针,一个begin,一中current,一后end,begin与current都初始化指向数组首部,end初始化指向数组尾部。

    1. current遍历整个数组序列,current指1时,不交换,current++;

    2. current指0时,与begin交换,而后current++,begin++;

    3. current指2时,与end交换,而后,current不动,end--。

 1 while (current <= end) {
 2     if (array[current] == 0) {
 3         swap(array[current], array[begin]);
 4         current++;
 5         begin++;
 6     } else if (array[current] == 1) {
 7         current++;
 8     } else {
 9         swap(array[current], array[end]);
10         end--;
11     }
12 }

二、最小的k个数

  输入n个整数,输出其中最小的k个。

  例如输入1,2,3,4,5,6,7,8这8个数字,则最小的4个数字为1,2,3,4

  解答:分析:这到底最简单的思路莫过于把输入的n个整数排序,这样排在最前面的k个数就是最小的k个数。只是这种思路的时间复杂度为O(nlgn)。我们试着寻找更快的解题思路。

  我们设最小的k个数中最大的数为A。在快速排序算法中,我们现在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在他的左边,比选中的数字大的数字都排在它的右边(即快排一次排序)。如果这个选中的数字的下标刚好是k-1(下标从0)开始,那么这个数字(就是A)加上左侧的k-1个数字就是最小的k个数。

  如果它的下标大于k-1,那么A应该位于它的左边,我们可以接着在它的右边部分的数组中寻找。可见这是一个递归问题,但是注意我们找到的k个数不一定是有序的。

 1 int Partition(int a[], int p, int r) {
 2     int pivot = a[r];
 3     int i = p - 1;
 4     for (int j = p; j <= r - 1; ++j) {
 5         if (a[j] <= pivot) {
 6             ++i;
 7             swap(a[i], a[j]);
 8         }
 9     }
10     swap(a[i + 1], a[r]);
11     return i + 1;
12 }
13 void GetLeastKNum(int *input, int n, int k) {
14     if (input == NULL || n <= 0 || k > n || k <= 0)
15         return;
16
17     int start = 0;
18     int end = n - 1;
19     int index = Partition(input, start, end);
20     while (index != k - 1) {
21         if (index < k - 1) {
22         start = index + 1;
23         index = Partition(input, start, end);
24         } else {
25             end = index - 1;
26             index = Partition(input, start, end);
27         }
28     }
29
30     for (int i = 0; i <= k - 1; ++i) {
31         cout << input[i];
32     }
33     cout << endl;
34 }

  上述方法的时间复杂度是O(n)。

转载于:https://www.cnblogs.com/vincently/p/4522957.html

【经典算法】快速排序相关推荐

  1. 经典算法—快速排序(Quicksort)使用详解

    快速排序(Quicksort)是对冒泡排序的一种改进.由C. A. R. Hoare在1962年提出.它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的 ...

  2. 经典算法(4)图解快速排序算法及代码实现

    写在前面: 我是「扬帆向海」,这个昵称来源于我的名字以及女朋友的名字.我热爱技术.热爱开源.热爱编程.技术是开源的.知识是共享的. 这博客是对自己学习的一点点总结及记录,如果您对 Java.算法 感兴 ...

  3. 经典算法之快速排序法(附B站最细讲解视频)

    活动地址:21天学习挑战赛 文章目录 一.算法 1.算法概述 2.算法步骤 3.算法特点 二.算法实践 1.Java代码 2.执行结果 3.讲解视频 三.复杂度分析 1.时间复杂度 2.空间复杂度 一 ...

  4. 十大经典排序算法-快速排序算法详解

    十大经典排序算法 十大经典排序算法-冒泡排序算法详解 十大经典排序算法-选择排序算法详解 十大经典排序算法-插入排序算法详解 十大经典排序算法-希尔排序算法详解 十大经典排序算法-快速排序算法详解 十 ...

  5. 经典算法07 快速排序

    ​经典算法07 快速排序 ​ 活动地址:CSDN21天学习挑战赛 *学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩:迟一天就多一天平庸的困扰. 算法思想: 在待排序表L[1-n]中任取一个元素p ...

  6. 经典算法书籍推荐以及算法书排行【算法四库全书】

    经典算法书籍推荐以及算法书排行[算法四库全书] 作者:霞落满天   https://linuxstyle.blog.csdn.net/    https://blog.csdn.net/21aspne ...

  7. 数据结构经典算法集锦

    数据结构经典算法集锦 第2章 线性表 KMP算法 //获得next数组 void GetNext(char *t, int next[MAX]) {int i = 1, j = 0;next[1] = ...

  8. 「干货」编程语言十大经典算法,你知道几个?

    算法与数据结构是计算机学习路上的内功心法,也是学好编程语言的重要基础.今天给大家介绍一下十大经典算法. 十大经典算法分别是:冒泡排序,插入排序,选择排序,希尔排序,快速排序,归并排序,桶排序,堆排序, ...

  9. 程序员必须要掌握的十大经典算法

    算法一:快速排序算法 快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个项目要Ο(n log n)次比较.在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见.事实上,快速排序通 ...

  10. java架构师进阶之独孤九剑(一)-算法思想与经典算法

    " 这是整个架构师连载系列,分为9大步骤,我们现在还在第一个步骤:程序设计和开发->数据结构与算法. 我们今天讲解重点讲解算法. 算法思想 1 贪心思想 顾名思义,贪心算法总是作出在当 ...

最新文章

  1. Windows和Linux的编译理解
  2. 陈长沙:学习者参考手册
  3. Jmeter脚本录制(APP)
  4. OV7725学习之SCCB协议(一)
  5. java数据类型的站位_Java 数据类型在实际开发中应用
  6. 计算机网络原理期末复习提纲,《计算机网络原理》考试复习提纲.doc
  7. Win32 控件篇(6)
  8. 深度学习项目:歌词的自动生成
  9. HTTP协议和HTTPS协议
  10. 上海交通大学计算机应用作业,上海交通大学继续教育学院计算机应用基础(二)第六次作业计算机安全多媒体_1...
  11. 【知识图谱系列】基于生成式的知识图谱预训练模型GPT-GNN
  12. 实现网站的国际化语言切换
  13. 唤起高德app执行导航
  14. 【JAVA】 new ArrayList<> () {{}} 双花括号 是什么写法?
  15. 基于Angularjs框架实现HTML5在线查看OFD文件
  16. mysql 8.0 新特性 统计直方图 优化执行计划SQL查询
  17. uni-app返回上一级并刷新页面
  18. CS5801|替代LT6711A|HDMI转DP转接线方案|HDMI转DP带供电芯片方案
  19. comsol纳米光学案例分析
  20. 理解SVM ——入门SVM和代码实现

热门文章

  1. 如何调整金格电子章服务器印章_大型集团公司的印章管理方法
  2. 心音数据库_小V云端数据库 | 2020.9.14—2020.9.18
  3. tensorboard merge报错_什么是TensorBoard?
  4. mongodb java项目 源码_spring项目整合mongodb进行开发
  5. matlab白化滤波,基于预白化方法的降噪预处理技术与流程
  6. js能关闭HTML页面,javascript可以关闭吗
  7. linux firefox xvfb,自动化测试之linux+xvfb+selenium+firefox+python测试环境搭建与测试
  8. java语言基础final_java语言中final的用法
  9. MPlayer编译步骤
  10. 机器学习(17)无监督学习 -- K-means算法与性能评估