首页 > 【经典算法】快速排序

【经典算法】快速排序

  与归并排序一样,快速排序使用也使用了分治的思想。下面是对一个典型的子数组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+112 }

  注意:上述算法有一个特点,即一次划分后,枢纽左边的相对位置不变。比如,原始序列:[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

更多相关:

  •         Apache POI是一个开源的利用Java读写Excel,WORD等微软OLE2组件文档的项目。        我的需求是对Excel的数据进行导入或将数据以Excel的形式导出。先上简单的测试代码:package com.xing.studyTest.poi;import java.io.FileInputSt...

  • 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1; 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。 要取得a到b之间的...

  • 利用本征图像分解(Intrinsic Image Decomposition)算法,将图像分解为shading(illumination) image 和 reflectance(albedo) image,计算图像的reflectance image。 Reflectance Image 是指在变化的光照条件下能够维持不变的图像部分...

  • 题目:面试题39. 数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 输出: 2 限制: 1 <= 数组长度 <= 50000 解题: cl...

  • 题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树:      5     /    2   6   /  1   3示例 1: 输入: [1,6,3,2,5] 输出...

  • 一、 介绍sort命令是用来对文字内容(文档)排序使用的。同时也可以排序去重、指定字段排序,按照月份排序、按照数字排序,检查文件是否有序等等。默认情况是按照字典序排序以后标准输出到屏幕上,但是该命令不会修改原来的文档内容。sort命令通常和uniq命令以及wc命令一起使用。二、 用法sort [OPTION]... [FILE]......

  • arr.sort((prev, next) => next.排序字段 - prev.排序字段);//从大到小降序(会改变原数组)arr.sort((prev, next) => prev.排序字段 - next.排序字段);//从小到大升序(会改变原数组)...

  • 以下为我们经常用到的十大典型排序算法导图,很多设计以及优化的思想值得去参考学习 因为代码较多,所以都添加到对应的实现注释中了,相关代码可以从Mind-mapping获取xmind源文件 参考文档: 基数排序 堆排序 希尔排序 https://blog.csdn.net/real_lisa/article/details/826854...

  • 对文本操作进行排序,以行为单位,依次根据ascii值进行比较,默认的排序方式为升序 sort [-bcfMnrtk][源文件][-o 输出文件]补充说明:sort可针对文本文件的内容,以行为单位来排序。 参  数:-b 忽略每行前面开始出的空格字符。-c 检查文件是否已经按照顺序排序。-f 排序时,忽略大小...

  • 下面链接中是我用jQuery的扩展来实现的表格分页和排序,使用这个扩展必须加上表头和标签,因为我是 通过来进行分页的,要是不加thead,那么表头也要作为分页计算时的一个行了。 下载最新代码和示例:jqueryPaging.rar 使用方法如下: