排序算法(2)、经典算法(7):快速排序算法

来源:互联网 发布:淘宝店主首页寄语 编辑:程序博客网 时间:2024/06/05 00:36

概述


快速排序,确实是一种排序算法,归到排序算法类别下。

又因为确实是经典算法,也就归到经典算法类别下了。

时间复杂度,最好为O(n),最差为O(n^2),平均为O(nlog(n))。为什么?用递归的写法去推?


在快速排序算法中,使用了分治策略。首先把序列分成两个子序列,递归地对子序列进行排序,直到整个序列排序结束。
步骤如下:
在序列中选择一个关键元素做为轴;
对序列进行重新排序,将比轴小的元素移到轴的前边,比轴大的元素移动到轴的后面。在进行划分之后,轴便在它最终的位置上;
递归地对两个子序列进行重新排序:含有较小元素的子序列和含有较大元素的子序列。

以下,每次挑选pivor变量的时候,都是选的A[low]。并不是随机选的(即,不是随机快速排序。)


代码实现


多增加一个数组


快速排序的函数。
有个问题:快速排序为什么能保证,low>=high的时候结束,是正确的?
二分查找也是,为什么low>high的时候,查找就结束了?
什么时候low和high会交叉?
void quickSort(int * A, int low, int high) {// 当low>=high的时候,结束// 当low<high的时候,继续排序if (low < high){int midIdx = FindMidIdx(A,low,high); // 排序,并找到中轴元素的下标quickSort(A,low,midIdx-1); // 对左边的进行排序quickSort(A,midIdx+1,high); //对右边的进行排序}}
对于quickSort这个函数需要说明的是,先调用找中轴、排序的函数。把一个长的序列分成两个小的。
左边的都比中轴小,右边的都不比中轴小。
假设左右两边都是排好了的序列,那么整个序列也排列好了。于是再对左边、右边的序列分别再排。

排序、找中轴元素的下标的函数。
int FindMidIdx(int * A, int low, int high) {int lowReserve = low; // 保存要对A的一部分排序的范围// 先开一个临时数组Bint N = high - low + 1; // B的大小int * B = new int[N];int j = 0; // 为了填充B,造出来的下标 B里的lowint k = N-1; // B里的high// 找中轴变量int pivor = A[low]; // A的low是中轴low++; // 从第二个开始起,开始找while (low<=high) // 从左往右扫{int temp = A[low];if (temp<pivor){B[j] = temp;j++;} else {B[k] = temp;k--;}low++;}B[j] = pivor;int midIdx = j + lowReserve; // A中的中间位置 for (int i = 0;i<N;i++) {  A[lowReserve+i] = B[i]; }delete[] B;return midIdx;}
还是那句话,用了更多的空间,换了简单的思维。开了一个临时数组B。
每次找中轴,都是找的一个数组中第1个元素。
从第二个开始扫描,直到末尾。把比较后的结果都放到B里面,最后把中轴变量放入B的“中间”位置,在代码中是用j来记录的。
最后,把B中的所有元素又赋值给A对应的部分。
需要注意的是,B是从0开始,到这次需要排序的数组的大小-1。而A是原始的数组。大小一直不变。
所以用下标访问的时候,访问B的下标和访问A的下标,写法是不同的 。

整个程序是这样的。
#include <iostream>using namespace std;const int global_N = 10;int FindMidIdx(int * A, int low, int high) {int lowReserve = low; // 保存要对A的一部分排序的范围int highReserve = high;// 先开一个临时数组Bint N = high - low + 1; // B的大小int * B = new int[N];int j = 0; // 为了填充B,造出来的下标 B里的lowint k = N-1; // B里的high// 找中轴变量int pivor = A[low]; // A的low是中轴low++; // 从第二个开始起,开始找while (low<=high) // 从左往右扫{int temp = A[low];if (temp<pivor){B[j] = temp;j++;} else {B[k] = temp;k--;}low++;}B[j] = pivor;int midIdx = j + lowReserve; // A中的中间位置 for (int i = 0;i<N;i++) {  A[lowReserve+i] = B[i]; }delete[] B;return midIdx;}void quickSort(int * A, int low, int high) {// 当low>=high的时候,结束// 当low<high的时候,继续排序if (low < high){int midIdx = FindMidIdx(A,low,high); // 排序,并找到中轴元素的下标for (int i = 0;i<global_N;i++){cout<<A[i]<<" ";}cout<<"\n";quickSort(A,low,midIdx-1); // 对左边的进行排序quickSort(A,midIdx+1,high); //对右边的进行排序}}int main() {int A[global_N] = {4,2,66,88,3,1,7,49,1,10};cout<<"原始数据"<<endl;for (int i = 0;i<global_N;i++){cout<<A[i]<<" ";}cout<<"\n";cout<<"开始快速排序"<<endl;quickSort(A,0,global_N-1);system("pause");return 0;}
原始数据
4 2 66 88 3 1 7 49 1 10
开始快速排序
2 3 1 1 4 10 49 7 88 66
1 1 2 3 4 10 49 7 88 66
1 1 2 3 4 10 49 7 88 66
1 1 2 3 4 7 10 66 88 49
1 1 2 3 4 7 10 49 66 88

有技巧的排序


如果在findMidIdx里面,不开辟临时数组B,就更有技巧了。
假设把A的第一个元素当作中轴变量pivor,就从A的右往左搜索,直到找到比pivor小的,放在low的位置,low++,再从左往右找,直到结束。
有个问题:快速排序为什么能保证,low>=high的时候结束,是正确的?
int findMidIdx(int A[], int low, int high) {int pivor = A[low]; // 第一个元素是pivorwhile (low < high){// 从右往左找while(A[high]>=pivor && low<high){high--; // 继续往左走} // 直到找到比pivor小的了,应该放在左边了A[low] = A[high];// low++; // 千万不能有这句话!// 从左往右找while(A[low]<pivor && low<high){low++;}A[high] = A[low];// high--;// 也千万不能有这句话!}int midIdx = low;A[midIdx] = pivor;return midIdx;}
quickSort函数与上面的一样。
当在main中对以下这个数组排序时
int A[g_N] = {4,2,66,88,3,1,7,49,1,10};
quickSort(A,0,g_N-1);
结果
1 2 1 3 4 88 7 49 66 10
1 2 1 3 4 88 7 49 66 10
1 1 2 3 4 88 7 49 66 10
1 1 2 3 4 10 7 49 66 88
1 1 2 3 4 7 10 49 66 88
1 1 2 3 4 7 10 49 66 88
代码比第一个版本要简洁很多。非常有技巧了。
注意,在赋值之后,low++和high--都是不能写的。如果这样写了以后,可能会跳过一些数据。
同时,在while里从右往左或从左往右搜索时,当low>=high,都必须是结束条件。
low和high的变换,让while去控制就好了。




如何对数组进行引用?
int n3[3] = {2, 4, 6};
int (&rn3)[3] = n3;     // A reference to an array of 3 ints


选一个更好的pivor


因为pivor选择的好坏,就直接影响到排序的效率。所以希望每次挑选的pivor都是一组数据中在中间位置的数。
有一个在一堆数中,找出第K大的元素的算法。递归调用。和findMidIdx的道理差不多。
之后的文章会总结这个算法,所以在这里就不多说了。

随机快速排序


随机快速排序,就是在每次选pivor的时候,随机地选一个数,而不是选A[low]。
那是不是每次都在选pivor之前,先随机生成一个下标,把A[下标]和A[low]交换,然后继续把A[low]作为pivor呢?
0 0
原创粉丝点击