快速排序是一个十分伟大的算法,作为再一次的学习,写一写快排以及和快排相关的问题。

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的几个用法:

1).对一维数组进行排序:
对一个长为1000的数组进行排序,int a[1000];
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).对二维数组进行排序:

int a[1000][2];其中按照a[0]的大小进行一个整体的排序,其中a[1]必须和a[0]一起移动交换;
qsort( a , 1000 , sizeof(int)*2 , cmp );
int cmp( const void *a , const void *b )
{return ( (int *)a)[0] - ( (int *)b)[0]  ;
}
char a[1000][20];进行排序:
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);

③用的是qsort,效果应该和②是一样的。
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*类型字符串进行排序:

以下是摘自stackoverflow的内容,讲的还算清楚。

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)相关推荐

  1. 快速排序(quicksort)算法实现

     快速排序(quicksort)是分治法的典型例子,它的主要思想是将一个待排序的数组以数组的某一个元素X为轴,使这个轴的左侧元素都比X大,而右侧元素都比X小(从大到小排序).然后以这个X在变换后数组的 ...

  2. qtdesigner怎么实现菜单栏跳转_3种公众号菜单栏设置类型,手把手教你做,不会的话那就再看一遍...

    常见的菜单栏设置怎么去设置呢?在我们的公众号左侧的菜单栏中,你可以找到我们的自定义菜单,这个功能,点击进去之后,你就可以看到菜单的内容,它可以有三种类型可选:一种叫发送消息,一种叫跳转网页,一种叫跳转 ...

  3. 长得类似铁甲小宝的机器人_铁甲小宝:小时候只顾看机器人忽略重点,长大后再看:是我太天真...

    铁甲小宝:小时候只顾看机器人忽略重点,长大后再看:是我太天真 铁甲小宝相亲大家都是看过的,作为早期的三大人特摄之一,铁甲小宝针对的完全就是儿童,小时候我们也是很喜欢这部作品,只是现在在荧幕上已经很难看 ...

  4. OpenCV学习笔记(四十一)——再看基础数据结构core OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年 OpenCV学习笔记(四十三)——存取像素值操作汇总co

    OpenCV学习笔记(四十一)--再看基础数据结构core 记得我在OpenCV学习笔记(四)--新版本的数据结构core里面讲过新版本的数据结构了,可是我再看这部分的时候,我发现我当时实在是看得太马 ...

  5. android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升

    android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升 转载于:https://www.cnblogs.com/jeanschen/p/3507512.html

  6. 再看数据库——(2)视图

    概念 *是从用户使用数据库的观点来说的. *从一个或多个表(视图)中导出来的 *一个虚表,或者说查询表 为什么要用视图呢? 一是简单,看到的就是需要的.视图不仅可以简化用户对数据的理解,也可以简化他们 ...

  7. 以前看书时记得一些笔记(二),很早了,现在再看都有些看不懂了

    MFC学习: 1.CObject类为MFC总类,该类下面有一个重要的类CCmdTarget.而CCmdTarget类下面又有四个重要的继承类,分别为:CWinThread.CDocument.CDoc ...

  8. 再看结构体对齐与小端联合问题

    再看结构体对齐与小端联合问题 @(组成原理) 先再次回看一道题目的分析. (2012.15)某计算机存储器按字节编址,采用小端方式存放数据.假定编译器规定int型和short型长度分别为32位和16位 ...

  9. 【发布】哔哩哔哩bilibili替换旧版播放(稍后再看)

    今天上B站发现强制界面新版了,连旧版切换按钮也隐藏了,目前还能通过[稍后再看]来切换旧版,随便写了个脚本,油猴新建脚本,添加以下 Greasy Fork地址: https://greasyfork.o ...

  10. 再看《西游记》——吴承恩眼中的现实社会是如何折射到《西游记》中的

    一.<西游记>其实是一个时代的映照,是一种启蒙思潮的载体,只不过它所代表的启蒙思潮却在中国被扼杀掉了 对于<西游记>,我们给与了较多.较高的美学评价,但如果只是把<西游记 ...

最新文章

  1. pandas使用query函数基于判断条件获得dataframe中满足条件的数据行(row)的索引列表(index of rows matching conditions in dataframe)
  2. 玩转spring boot——结合阿里云持续交付
  3. Spring Boot 系列(八)@ControllerAdvice 拦截异常并统一处理
  4. 邮箱的正则表达式验证总结经验
  5. virtual box挂载 共享文件夹
  6. K8s 学习者绝对不能错过的最全知识图谱(内含 58个知识点链接)
  7. Python 十七章 Web开发
  8. hadoop学习2 记录配置hadoop环境的那些坑
  9. 输入两个长度相同的字符串,比较两个数在相同位置的字符是否相同
  10. jquery 获取checkbox 或 select 的选中值(一组和单个)
  11. 创新与创业的良性共存 又拍云Open Talk NO.20开讲
  12. freeswitch modules 模块
  13. STL是什么(STL简介)
  14. php去除空格和换行
  15. 51c语言编程基础,51单片机c语言编程入门(详讲版)
  16. Conficker蠕虫病毒只是愚人节玩笑吗
  17. 双绞线的规范和制作经验谈
  18. FPGA Verilog语言常用语法
  19. 浅谈python运算符运算法则
  20. Linux控制Nvidia显卡风扇转速

热门文章

  1. 今年职高计算机数学高考试题,湖南职高对口数学高考试卷
  2. ABAQUS的第一个error【删去Job-1.lck】
  3. bugkuCTF—杂项—旋转跳跃
  4. WPF实现半圆形导航菜单
  5. 投票男神女神公众号投票系统_男神女神投票 v5.5.21版本
  6. 8、项目管理基础知识
  7. Android程序无响应(ANR)日志抓取
  8. 计算机无法显示移动硬盘,谁知道移动硬盘在电脑显示不出来是怎么回事?
  9. AutoCAD Electrical 2020 安装后无激活界面
  10. 微软商店安装包_闲着不如折腾,教你现在就尝鲜年底才发售的「微软双屏手机」...