再看快速排序(QuickSort)
快速排序是一个十分伟大的算法,作为再一次的学习,写一写快排以及和快排相关的问题。
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;
}
2).对二维数组进行排序:
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.
5.2.sort
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)是分治法的典型例子,它的主要思想是将一个待排序的数组以数组的某一个元素X为轴,使这个轴的左侧元素都比X大,而右侧元素都比X小(从大到小排序).然后以这个X在变换后数组的 ...
- qtdesigner怎么实现菜单栏跳转_3种公众号菜单栏设置类型,手把手教你做,不会的话那就再看一遍...
常见的菜单栏设置怎么去设置呢?在我们的公众号左侧的菜单栏中,你可以找到我们的自定义菜单,这个功能,点击进去之后,你就可以看到菜单的内容,它可以有三种类型可选:一种叫发送消息,一种叫跳转网页,一种叫跳转 ...
- 长得类似铁甲小宝的机器人_铁甲小宝:小时候只顾看机器人忽略重点,长大后再看:是我太天真...
铁甲小宝:小时候只顾看机器人忽略重点,长大后再看:是我太天真 铁甲小宝相亲大家都是看过的,作为早期的三大人特摄之一,铁甲小宝针对的完全就是儿童,小时候我们也是很喜欢这部作品,只是现在在荧幕上已经很难看 ...
- OpenCV学习笔记(四十一)——再看基础数据结构core OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年 OpenCV学习笔记(四十三)——存取像素值操作汇总co
OpenCV学习笔记(四十一)--再看基础数据结构core 记得我在OpenCV学习笔记(四)--新版本的数据结构core里面讲过新版本的数据结构了,可是我再看这部分的时候,我发现我当时实在是看得太马 ...
- android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升
android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升 转载于:https://www.cnblogs.com/jeanschen/p/3507512.html
- 再看数据库——(2)视图
概念 *是从用户使用数据库的观点来说的. *从一个或多个表(视图)中导出来的 *一个虚表,或者说查询表 为什么要用视图呢? 一是简单,看到的就是需要的.视图不仅可以简化用户对数据的理解,也可以简化他们 ...
- 以前看书时记得一些笔记(二),很早了,现在再看都有些看不懂了
MFC学习: 1.CObject类为MFC总类,该类下面有一个重要的类CCmdTarget.而CCmdTarget类下面又有四个重要的继承类,分别为:CWinThread.CDocument.CDoc ...
- 再看结构体对齐与小端联合问题
再看结构体对齐与小端联合问题 @(组成原理) 先再次回看一道题目的分析. (2012.15)某计算机存储器按字节编址,采用小端方式存放数据.假定编译器规定int型和short型长度分别为32位和16位 ...
- 【发布】哔哩哔哩bilibili替换旧版播放(稍后再看)
今天上B站发现强制界面新版了,连旧版切换按钮也隐藏了,目前还能通过[稍后再看]来切换旧版,随便写了个脚本,油猴新建脚本,添加以下 Greasy Fork地址: https://greasyfork.o ...
- 再看《西游记》——吴承恩眼中的现实社会是如何折射到《西游记》中的
一.<西游记>其实是一个时代的映照,是一种启蒙思潮的载体,只不过它所代表的启蒙思潮却在中国被扼杀掉了 对于<西游记>,我们给与了较多.较高的美学评价,但如果只是把<西游记 ...
最新文章
- pandas使用query函数基于判断条件获得dataframe中满足条件的数据行(row)的索引列表(index of rows matching conditions in dataframe)
- 玩转spring boot——结合阿里云持续交付
- Spring Boot 系列(八)@ControllerAdvice 拦截异常并统一处理
- 邮箱的正则表达式验证总结经验
- virtual box挂载 共享文件夹
- K8s 学习者绝对不能错过的最全知识图谱(内含 58个知识点链接)
- Python 十七章 Web开发
- hadoop学习2 记录配置hadoop环境的那些坑
- 输入两个长度相同的字符串,比较两个数在相同位置的字符是否相同
- jquery 获取checkbox 或 select 的选中值(一组和单个)
- 创新与创业的良性共存 又拍云Open Talk NO.20开讲
- freeswitch modules 模块
- STL是什么(STL简介)
- php去除空格和换行
- 51c语言编程基础,51单片机c语言编程入门(详讲版)
- Conficker蠕虫病毒只是愚人节玩笑吗
- 双绞线的规范和制作经验谈
- FPGA Verilog语言常用语法
- 浅谈python运算符运算法则
- Linux控制Nvidia显卡风扇转速
热门文章
- 今年职高计算机数学高考试题,湖南职高对口数学高考试卷
- ABAQUS的第一个error【删去Job-1.lck】
- bugkuCTF—杂项—旋转跳跃
- WPF实现半圆形导航菜单
- 投票男神女神公众号投票系统_男神女神投票 v5.5.21版本
- 8、项目管理基础知识
- Android程序无响应(ANR)日志抓取
- 计算机无法显示移动硬盘,谁知道移动硬盘在电脑显示不出来是怎么回事?
- AutoCAD Electrical 2020 安装后无激活界面
- 微软商店安装包_闲着不如折腾,教你现在就尝鲜年底才发售的「微软双屏手机」...