排序算法(2)、经典算法(7):快速排序算法
2017-01-02 22:29
197 查看
概述
快速排序,确实是一种排序算法,归到排序算法类别下。又因为确实是经典算法,也就归到经典算法类别下了。
时间复杂度,最好为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的一部分排序的范围 // 先开一个临时数组B int N = high - low + 1; // B的大小 int * B = new int ; int j = 0; // 为了填充B,造出来的下标 B里的low int 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; // 先开一个临时数组B int N = high - low + 1; // B的大小 int * B = new int ; int j = 0; // 为了填充B,造出来的下标 B里的low int 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]; // 第一个元素是pivor while (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};
选一个更好的pivor
因为pivor选择的好坏,就直接影响到排序的效率。所以希望每次挑选的pivor都是一组数据中在中间位置的数。有一个在一堆数中,找出第K大的元素的算法。递归调用。和findMidIdx的道理差不多。
之后的文章会总结这个算法,所以在这里就不多说了。
随机快速排序
随机快速排序,就是在每次选pivor的时候,随机地选一个数,而不是选A[low]。那是不是每次都在选pivor之前,先随机生成一个下标,把A[下标]和A[low]交换,然后继续把A[low]作为pivor呢?
相关文章推荐
- 经典算法-C#四种排序算法
- 【每日算法】C语言8大经典排序算法(2)
- 经典算法-C#四种排序算法
- 算法:一些 4000 经典的排序算法
- 算法之经典排序算法小归纳
- 【每日算法】C语言8大经典排序算法(2)
- 经典算法-C#四种排序算法
- 经典算法-C#四种排序算法
- as3版经典算法--快速排序算法(一)
- 经典算法学习:各种排序算法的模板类实现
- 白话经典算法-常见排序算法的实现与性能比较
- 经典算法-C#四种排序算法
- 经典算法-C#四种排序算法
- 经典算法大总结之排序算法
- Java 常用排序算法/Java经典问题算法
- 经典算法学习————快速排序算法的c++实现
- 总结下排序算法——MoreWindows白话经典算法之七大排序总结篇
- 经典算法(8)- 插入排序(Insertion Sort) 及三个基本排序算法的比较
- 经典算法大总结之排序算法
- 算法之经典排序算法小归纳