再看快速排序(QuickSort)
来源:互联网 发布:javascript delay函数 编辑:程序博客网 时间:2024/06/05 16:24
快速排序是一个十分伟大的算法,作为再一次的学习,写一写快排以及和快排相关的问题。
1.基本的快速排序方法。
快速排序(QuickSort)的基本思想是:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可以分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
快速排序的基本过程在此不做赘述,主要展示代码以及和快排相关的问题。当然是先快排有很多种方法很代码,基本程序的框架是一样的。
代码:
#include<iostream>using namespace std;template <typename T>void QuickSort(T *a,int low , int high){int pivot ;if(low < high){pivot = Partition(a,low,high);//算出枢轴将数组a以pivot一分为二 QuickSort(a,low,pivot-1); //对低子表递归排序 QuickSort(a,pivot+1,high); //对高子表递归排序 }}/*Partition函数要做的,就是先选取当中的一个关键字,比如选择第一个关键字50,然后将它放到某一个位置,使得它左边的 值都比它小,右边的值比它大。*/ template <typename T>int Partition(T *a,int low,int high){int pivot_key = a[low];//用子表的第一个记录作为枢轴记录 /*从表的两端交替向中间扫描*/ while( low < high ){ /*从后向前扫描*/while( low<high && a[high]>=pivot_key ){high--;}a[low] = a[high]; /*从前向后扫描*/while( low<high && a[low]<=pivot_key ) { low++;}a[high] = a[low];}a[low] = pivot_key;return low; //返回枢轴所在的位置 }template <typename T>void print(T *a , int len){for(int i=0;i<len;i++){cout<<a[i]<<" ";}cout<<endl;}int main(){int a[] = {50,10,90,30,70,40,80,60,20};char b[] = {'e','a','i','c','g','d','h','f','b'};QuickSort( a,0,sizeof(a)/sizeof(a[0])-1 );cout<<"After QSort:"<<endl; print( a,sizeof(a)/sizeof(a[0]) );QuickSort( b,0,sizeof(b)/sizeof(b[0])-1 ); cout<<"After QSort:"<<endl;print( b,sizeof(b)/sizeof(b[0]) ); system("pause");return 0;}
结果:
2.快排的优化
2.1优化选取枢轴
三数取中法:取三个关键字先进性排序,将中间数作为枢轴,一般是曲左端、右端和中间三个数。这样至少中间数一定不会是最小或者最大的数,从个概率上来讲,取三个数均为最小或者最大数的可能性微乎其微,因此中间数位于较为中间的值的可能性就大大提高了。因此可以在Partition函数的第一行代码(int pivot_key = a[low];)前加入如下代码:
int m = ( (high - low) >> 1 ) + low;//计算数组中间元素的下标if( a[low] > a[high]) { swap(a,low,high);}if( a[m] > a[high] ){ swap(a,high,m);}if( a[m] > a[low] ){ swap(a,m,low);}/*此时a[low]已经为整个序列左中右三个关键字的中间值*/int pivot_key = a[low];.... </span>
2.2优化小数组的排序方案
快排适合于解决非常大的数组的排序问题。那么相反的情况下,如果数组非常小,其实快排反而不如插入排序来的效果更好(直接插入排序是简单排序中性能效果最好的)。因为快排用了很多递归操作,在大量数据排序时,算法优势胜过递归影响,但如果只有几个记录,可以选择插入排序。
2.3优化递归操作
递归对性能是有一定影响的,Quicksort函数在其尾部有两次递归操作。如果待排序的序列划分极端不平衡,递归深度将趋近于n,而不是平衡时的log2n,这就不仅仅是速度快排的问题了。栈的大小是很有限的,每次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多。因此如果能够减少递归,将会大大提高性能。于是对QuickSort实施尾递归优化。
template <typename T>void QuickSort1(T *a,int low , int high){int pivot ;<strong>while</strong>(low < high){pivot = Partition(a,low,high);//算出枢轴将数组a以pivot一分为二 QuickSort1(a,low,pivot-1); //对低子表递归排序 <strong>low = pivot + 1 ; //尾递归 </strong>}}当我们将if改成while后,因为第一次递归以后,变量low就没有用处了,所以可以将pivot+1赋值给low,在循环后,来一次Partition(a,low,high),其效果等同于"QuickSort(a,pivot+1,high)"。结果相同,但因为采用迭代而不是递归的方法可以缩减堆栈深度,从而提高整体性能。关于尾递归,笔者理解的也不是十分透彻,希望读者可以不吝赐教。
3.中位数问题:现在给你n个数,让你找到这n个数的中位数。有哪些方法?(假设n个数可以一次装入到内存中)
方法一:这个n个数是无序的,那么就去将这n个数进行排序,利用快速排序,平均时间复杂度为O(nlogn),然后用O(1)的时间找到中位数。具体代码就不写了。只是在n很大的情况下,效率非常的低,那么有木有线性复杂度的方法呢?
方法二:快排的变形。我们知道可以通过分治的方法将数组按照枢轴分为两个部分,一个是大于枢轴的部分,另一个是小于枢轴的部分,那么找到中位数就相当于找到枢轴等于(n-1)/2时候对应的数组的值,因此在每次得到一个枢轴值的时候,都和(n-1)/2进行比较,如果小于(n-1)/2,那么就去处理枢轴右面的数组序列;否则处理枢轴左面的数组序列,这样就相当于是一个线性的搜索过程,时间复杂度为O(n)。
而查找中位数也是另外一个问题的具体情况,那就是"The max/min Nth",数组中第N个最大/最小数的问题,其实也是相当于TopN问题,那么我们下面分析这个问题,并将代码呈上。
4.如何找到数组中最大(小)的第K个数?又如何找到数组中的前K个最大(小)的数,即TopN问题?(为方便讨论,下面都是找到最大的数)
方法一:首先可以使用堆排序
找到第k大的数以及TopK最大的数,可以使用堆排序,建立大顶推,不断的调整,经过k次,就可以找到最大的k个数,第k大的数自然也就得到了。经过k次调整,平均的时间复杂度为O(klogn)。代码在这里就不贴了。重点介绍方法二。
方法二:这种方法类似于3中介绍的方法二,就不再细说,上代码。
#include<iostream>using namespace std;template <typename T>T findTheKthNum(T *a,int low , int high, int nth){int pivot = Partition(a,low,high);if(pivot == nth) return a[pivot];else if( pivot > nth ) return findTheKthNum(a,low,pivot-1,nth);else return findTheKthNum(a,pivot+1,high,nth);}template <typename T>int Partition(T *a,int low,int high){int pivot_key = a[low];while( low < high ){while( low<high && a[high]>=pivot_key ){high--;}a[low] = a[high];while( low<high && a[low]<=pivot_key ){low++;}a[high] = a[low];}a[low] = pivot_key;return low;}template <typename T>void print(T *a , int len){for(int i=0;i<len;i++){cout<<a[i]<<" ";}cout<<endl;}int main(){int a[] = {50,10,90,30,70,40,80,60,20};char b[] = {'e','a','i','c','g','d','h','f','b'};cout<< findTheKthNum( a,0,sizeof(a)/sizeof(a[0])-1 , 4 )<<endl;print( a,sizeof(a)/sizeof(a[0]) );cout<< findTheKthNum( b,0,sizeof(b)/sizeof(b[0])-1 , 4 )<<endl;print( b,sizeof(b)/sizeof(b[0]) ); system("pause") ;return 0;}结果:
分析:这段代码就相当于找到了第K大的数,同时左边都是比它小的数,右边都是比它大的数,自然就能知道TopK小(大)的数了。
PS:现在有n个数,不能够一次性的装入到内存中,如何找到TopK大的数?这是一个大数据的算法问题,在此不做具体分了,大概的步骤是先对每个数hash取余到若干文件中,然后对每个文件中的用堆排序或者分治的方法得到最大的K个数,最后将每个文件中最大的K个数归并,得到整体的K个数。
5.最后讲一下C语言的里面的qsort函数以及C++中的sort函数,主要还是讲用法。
5.1.qsort
qsort的定义为:
void qsort(void *base,size_t num,size_t size,int(*compar)(const void*,const void*) );其中compar为函数指针,需要传递一个函数名来调用该函数,一般这种函数的原型为:
<span style="font-size:14px;">int compar(const void* a , const void *b) { return ( *(int*)a - *(int*)b );}</span>
关于函数指针在此就不做过多解释,主要还是写一下qsort的几个用法:
qsort( a , 1000 , sizeof(int) , cmp);int cmp( const void *a , const void *b ){ return *(int *)a-*(int *)b;//由大到小排序,return *(int*)b-*(int*)a;}
qsort( a , 1000 , sizeof(int)*2 , cmp );int cmp( const void *a , const void *b ){ return ( (int *)a)[0] - ( (int *)b)[0] ;}
qsort( a , 1000 , sizeof(char)*20 , cmp );int cmp( const void *a , const void *b ){ return strcmp( (char*)a - (char*)b ) ;}
3).对结构体进行排序:
typedef struct str{ char str1[11]; char str2[11];}str;str s[1000];int cmp(const void *a, const void *b){ return strcmp( ((str*)a)->str2 , ((str*)b)->str2 );}qsort( s , 1000 , sizeof(str) , cmp );
②对结构体进行排序,cmp函数实现了,先对dis从大到小排序,然后在dis相同的情况下,按照cost从大到小进行排序。
typedef struct point{ int dis; int cost; }tPoint ;tPoint p[10001];bool cmp(point a,point b){ if(a.dis < b.dis) return true; else if(a.dis == b.dis) return a.cost<b.cost; else return false;}sort(p,p+n,cmp);
typedef struct point{ int dis; int cost; }tPoint ;tPoint p[10001];int cmp_dis(const void *a , const void *b){ if ( ( ((tPoint*)a)->dis ) > ( ((tPoint*)b)->dis ) ) return true; else if ( ( ((tPoint*)a)->dis ) == ( ((tPoint*)b)->dis ) ) return ( ((tPoint*)a)->cost ) > ( ((tPoint*)b)->cost ); else return false;} qsort( p,n,sizeof(tPoint),cmp_dis );//sort by dis
4).对double型进行排序:
int cmp( const void *a, const void *b ){ return ( (*(double*)a - *(double*)b >0 )?1:-1 ;}qsort( s,n,sizeof(int ),cmp );5).对char*类型字符串进行排序:
Suppose I have an array of pointers to char in C:
char *data[5] = { "boda", "cydo", "washington", "dc", "obama" };
And I wish to sort this array using qsort:
qsort(data, 5, sizeof(char *), compare_function);
I am unable to come up with the compare function. For some reason this doesn't work:
int compare_function(const void *name1, const void *name2){ const char *name1_ = (const char *)name1; const char *name2_ = (const char *)name2; return strcmp(name1_, name2_);}
I did a lot of searching and found that I had to use **
inside of qsort:
int compare_function(const void *name1, const void *name2){ const char *name1_ = *(const char **)name1; const char *name2_ = *(const char **)name2; return strcmp(name1_, name2_);}
Then works.
qsort似乎不能体现出范型编程的优势,而C++中的sort相对来讲更简单易用写。
基本用法参考:http://www.cplusplus.com/reference/algorithm/sort/?kw=sort
关于sort中的第二个版本,定义仿函数来自己定义排序方法中的仿函数理解还是很模糊,根据书上的说法就是:
以sort()为例,其第一版本是以operator<为排序时的元素位置调整依据,第二版本则允许用户指定任何"操作",务必排序后的两两相邻元素都能令该结果为true。要将这种"操作"当做算法的参数,唯一办法就是先将该"操作"设计为一个所谓的仿函数(就语言层面而言是个class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。
根据以上陈述,既然函数指针可以达到"将整组操作当做算法的参数",那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求--函数指针无法和STL其他组件(如配接器adapter)搭配,产生更灵活的变化。
就是先观点而言,仿函数其实上就是一个"行为类似函数"的对象,为了能够"行为类似函数",其类别定义中必须自定义(改写,重载)functional call 运算子(operator())。拥有这样的运算子后,我们就可以在仿函数对象后而加上一对小括号,一次调用仿函数所定义的operator()。下面贴个代码,用到了sort和for_each。
#include<iostream>#include<vector>#include<algorithm>using namespace std;bool pfunc(int i,int j){ return i < j;}class cfunctor{public: bool operator()(int i,int j) { return i < j; }}mycfunctor;void pprint(int i){ cout<<i<<" ";}class cprint{public: void operator()(int i) { cout<<i<<" "; }}mycprint;int main(){ int a[] = {32,71,12,45,26,80,53,33}; int b[] = {32,71,12,45,26,80,53,33}; vector<int> vec1( a,a+sizeof(a)/sizeof(a[0]) ); vector<int> vec2( b,b+sizeof(b)/sizeof(b[0]) ); sort( vec1.begin(),vec1.end(),pfunc ); sort( vec2.begin(),vec2.end(),mycfunctor ); for_each(vec1.begin(),vec1.end(),pprint ); cout<<endl; for_each(vec2.begin(),vec2.end(),mycprint); cout<<endl; system("pause"); return 0;}
体会一下这个代码,知道怎么用,然后感受一些就行了。
转载请注明:http://blog.csdn.net/lavorange/article/details/38896519
- 再看快速排序(QuickSort)
- 快速排序(quickSort)
- 快速排序(Quicksort)
- 快速排序(QuickSort)
- 快速排序(Quicksort)
- 快速排序(Quicksort)
- 快速排序(Quicksort)
- 快速排序(Quicksort)
- 快速排序(QuickSort)
- 快速排序(Quicksort)
- 快速排序(QuickSort)
- 快速排序(Quicksort)
- 快速排序(Quicksort)
- 快速排序(QuickSort)
- 快速排序(Quicksort)
- 快速排序(QuickSort)
- 快速排序(QuickSort)
- QuickSort -- 快速排序(C++)
- DSP6000的上电及供电
- 读书札记-2013年12月
- DSP6000的几个简单优化技巧
- HttpURLConnection的使用
- toj1339
- 再看快速排序(QuickSort)
- 数字信号处理的学习资源
- vim的visual模式
- 白话压缩感知(含Matlab代码)
- XA,JMS,JNDI.JMX
- Firefox os 游戏开发之2048游戏源码
- UIimageview添加手势,无法响应手势事件
- OpenCV基础篇之绘图及RNG随机数对象
- 【BZOJ 3232】圈地游戏 分数规划+最大权闭合图