以前写过一次排序的程序,最近又讲了一次,瞬间无比的懂,所以写一个博客记录
快速排序的目的就是每一次都将比基准数大的所有数都放在基准数的右边,比基准数小的所有数都放在基准数的左边,然后递归左右,直到左右集合只有一个数或者没有数。
这里基准数取最左边的数.
首先模拟快速排序的过程:
4 6 7 1 2
第一步记录基准数key=4,同时i=0,j=4,这时可以认为4这里有一个坑(因为他的值已经被记住了)即 坑 6 7 1 2
第二步 从右开始向左找比key小的数(为什么是小,因为我们认为结果是从小到大,那么右边肯定是大的,如果你小了肯定不对),发现2比key小,所以将2填进坑
即 2 6 7 1 坑
第三步 从左边开始找比key大的数(左边应该是小的,如果大了你还是去右边吧)即 找到 6然后变成 2 坑 7 1 6
第三步 从右边找比key小的数,变成2 1 7 坑 6
第四步 从左边找比key大的数变成 2 1 坑 7 6
此时左右相遇,所以退出循环,坑里面放key,此时21 4 7 6
经过上面的操作成功使key左边的数都比它小,右边的数都比它大
然后让(2 1)再次这样,(7 6)再次这样可以得到排序结果。
这里要注意找的时候是不能含相等的,也就是只有找到比它小(或大)的数才填坑,因为如果相等就填坑会出现死循环!比如3 2 3 一开始 基准是3 从右边找找到小于等于3的,
变成 3 2 坑,然后从a[0]=3这个开始再找大于等于3的又找到3再交换 ,又变成 坑 2 3,死循环!
接下来讲一下快速排序为什么快!(这里主要是和O(n^2)的冒泡和选择)
我们可以想一个天平问题,有12个小球,有1个天平,称多少次可以称出那个较重(或轻)的小球,3次!
不用构造解,我们可以这样想,12个小球,有重有轻一共24个状态,然后天平自身有3种状态,(轻,相等,重)即是求3 ^k>=24可以解得k=3,这是最少个数,如果不比这么多,总是会有小球无法判断状态。
同理n个数进行全排列,一共有n!的情况,那么基于选择的排序每一次都是比较左右两边,有两种情况(大或者小,如果相等可以归结于大或者小中位置不变的那个),
所以2^k>=n!即得k>=log n! 这是基于选择的排序的下限,而这个结果接近于快速排序的O(nlog n)
我们可以这么想,我们10个数猜数字,一定是用折半的方法,这样次数最少,因为左右两边等可能,所以虽然数学证明很复杂,但我们能直观地感到左右两边等可能时效率最高,而 1,2,3,4这样找下去效率最低,而这不就是10个数排序的冒泡吗,每一次找出最小的数,次小的....而对于快速排序如果左右两边等量比如(2 1 )3 (5 4)那么效率最高,如果左边少一点,效率低一点,最低就是一边为空,比如3(5 4)那最低不就是冒泡吗,每一次找出最小的数~~可以得出快速排序确实是快
下面贴代码:
#include <stdio.h>
void quicksort(int a[],int left,int right);
int main()
{
int i,j,n;
int b;
int a[100];
printf("请输入数组长度\n");
scanf("%d",&n);
printf("请输入数组元素\n");
for (i=0;i<n;i++)
scanf("%d",&a[i]);
quicksort(a,0,n-1);
for (i=0;i<n;i++)
printf("%d ",a[i]);
printf("\n");
}
void quicksort(int a[],int left,int right)
{
int i,j,key,low,high;//key是基准
low=left;
high=right;
key=a[left];//以最左边的数为基准
if (left<right)//如果传递进来的还有元素
{
while (low<high)
{
while ((low<high)&&(a[high]>=key))//找到小于基准的数(为什么右边先动,如果第一次左边先动找到了大于key的位置,那它应该放在右边的哪里呢,显然不知道,而右边先动,一旦找到小于key的就和基准换位置,反正最后还要将基准塞进一个新位置)
{
high--;
}
a[low]=a[high];//小于的那个数应该是属于基准数左边的(如果第一次循环就正好代替了基准数,这也是使用key保留的原因)
while ((low<high)&&(a[low]<=key))//这里说明为什么要含等号,因为我们假设3,2,3.如果没有等号,第一次循坏跳过,第二次循环也跳过,然而low仍小于high,死循环!(而加了=,第一次就是2,此时2,2,3,第二次也是2,此时2,2,3,最后替换key,得2,3,3!
{
low++;//找到大于基准的数
}
a[high]=a[low];//他应该是属于右边的,还记得刚才high的值小于key被保存在左边了吗,现在正好把原来小的值覆盖掉换成大于基准的数
}//以上就是将所有大于key的数放在右边,小于key的数放在左边
a[low]=key;//最后一定是剩下key的值没有
quicksort(a,left,low-1);
quicksort(a,low+1,right);
}
}
每一次都这样肯定比较烦,所以c++有sort函数可以直接用(c语言也有,太麻烦了),包含<iostream>和<algorithm>如果习惯c语言可再用<cstdio>
用法是从小到大直接 sort(a,a+n);其中a是数组名(其实是首地址,你也可以从a+1,开始),n是比较多少个数,
如果从大到小需要 sort(a,a+n,cmp);同时写int cmp(int a,int b){return a>b;}
如果是结构体 需要 sort(a,a+n,cmp2)写 int cmp(student a,student b){return a.score>b.score)假设student里根据成绩从大到小排序
poj3664
http://poj.org/problem?id=3664
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef struct node
{
int a;
int b;
int index;
}node;
int cmp(node x,node y)
{
return x.a>y.a;
}
int cmp2(node x,node y)
{
return x.b>y.b;
}
int main()
{
int n,k,i;
node t[50005];
while(scanf("%d%d",&n,&k)==2)
{
memset(t,0,sizeof(t));
for (i=0;i<n;i++)
{
scanf("%d%d",&t[i].a,&t[i].b);
t[i].index=i+1;
}
sort(t,t+n,cmp); //第一次是根据a来判断
sort(t,t+k,cmp2);
printf("%d\n",t[0].index);
}
return 0;
}
Poj2388
http://poj.org/problem?id=2388
#include <stdio.h>
void quicksort(int a[],int left,int right);
int main()
{
int i,j,n;
int b;
int a[10005];
while(scanf("%d",&n)==1)
{
for (i=0;i<n;i++)
scanf("%d",&a[i]);
quicksort(a,0,n-1);
printf("%d\n",a[n/2]);
}
}
void quicksort(int a[],int left,int right)
{
int i,j,key,low,high;//key是基准
low=left;
high=right;
key=a[left];//以最左边的数为基准
if (left<right)//如果传递进来的还有元素
{
while (low<high)
{
while ((low<high)&&(a[high]>=key))//找到小于基准的数(为什么右边先动,如果第一次左边先动找到了大于key的位置,那它应该放在右边的哪里呢,显然不知道,而右边先动,一旦找到小于key的就和基准换位置,反正最后还要将基准塞进一个新位置)
{
high--;
}
a[low]=a[high];//小于的那个数应该是属于基准数左边的(如果第一次循环就正好代替了基准数,这也是使用key保留的原因)
while ((low<high)&&(a[low]<=key))//这里说明为什么要含等号,因为我们假设3,2,3.如果没有等号,第一次循坏跳过,第二次循环也跳过,然而low仍小于high,死循环!(而加了=,第一次就是2,此时2,2,3,第二次也是2,此时2,2,3,最后替换key,得2,3,3!
{
low++;//找到大于基准的数
}
a[high]=a[low];//他应该是属于右边的,还记得刚才high的值小于key被保存在左边了吗,现在正好把原来小的值覆盖掉换成大于基准的数
}//以上就是将所有大于key的数放在右边,小于key的数放在左边
a[low]=key;//最后一定是剩下key的值没有
quicksort(a,left,low-1);
quicksort(a,low+1,right);
}
}
各拿sort和手打的快排验证了一下