浅谈快速排序

       快速排序,简称快排,对于一个包含n个输入数组来说,快速排序是一种最坏情况时间复杂度为O(n^2)的排序算法,虽然最坏情况的时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为他的平均性能很好,他的期望时间复杂度为O(n lg n)。

快速排序的函数qsort,它包含在<stdlib.h>头文件里,函数一共四个参数,在函数头部加上头文件#include<stdlib.h>,就可以直接调用,并且无需声明。一个典型的qsort的写法如下:

qsort(s,n,sizeof(s[0]),cmp);

       其中,s代表数组的名字,或者也可以理解成开始排序的地址,因为可以写成&s[i]这样的表达式,n代表参与排序的个数,sizeof(s[0])代表每一个参与排序的个体的长度,cmp只是一种函数名字,他可以改成其他任意合法的函数名,它是为指导qsort如何进行排序而专门写的一个函数,我们称之为比较函数,其目的是为了告诉qsort要以什么样的方式进行排序(是升序?还是降序?或者按照某个关键字进行排序等)(注:写成cmp只是一个名字,可以随便怎么写),cmp这个函数有形参,和返回值(int型),但是在调用时却不需要给它传递实参进去,直接调用其名字即可,这个函数是专门为qsort开发的一种函数形式,这个函数的典型定义是:

int cmp(const void *a,const void *b);

规定这个函数只能返回int型值。

一.关于快排的一些小问题的解答:

No.1.快排的复杂度:

         当元素个数比较少时(10^2的数量级左右),快排的速度跟冒泡相比并没有快很多,还有如果要排序的元素大部分都已经是排好顺序了时,快排效率会下降,但是其最坏情况是N^2(当元素全部是已经排好的顺序时),一般情况(也即平均效率)是N*Log2(N),最好情况是N(当元素全部是逆序时),快排的特点是元素越乱排序速度越快,所以可以看出,虽然元素少时使用快排并没有很大优势,但是在快排的最坏情况跟冒泡、选择排序(冒泡、选择排序其复杂度不受元素顺序影响,永远为N^2)一样,所以快排永远是最快的。

No.2.快排的稳定性:

快排是不稳定的,这里的稳定性是指对于相同元素的处理上,快排会打乱相同元素的先后顺序,比如待排序数组int a[] ={1, 2, 2, 3, 4, 5, 6};此时两个2(原因就在于快排排   序原理,其排序过程是不断把元素分组打乱进行的),当然如果单纯排序一列数字是没什么区别的,假设我们有这样一列数字3a,3b,3c,但是三个3是有区别的,我们标记为3a,3b,3c,快排后的结果不一定就是3a,3b,3c这样的排列,所以在某些特定场合我们要用结构体来使其稳定。

No.3.快排的比较函数cmp

        cmp函数两个参数必须都是(const void *)的,这定义了一个指针a,a可以指向任意类型的值,但它指向的值必须是常量。所以可以对任何类型的常量进行排序,这个要特别注意,写a和b只是个人喜好,写成cmp也只是纯属个人喜好而已。

No.4.快排函数qsort(s,n,sizeof(s[0]),cmp)的第三个参数

        推荐是使用sizeof(s[0])这样,特别是对结构体,往往自己写成2*sizeof(int)这样的会出问题,用sizeof(s[0])既方便又保险。

No.5.对数组部分数进行排序

        比如对一个s[n]的数组,要对其从s[i]开始的m个元素进行排序,只需要在第一个和第二个参数上进行一些修改:qsort(&s[i],m,sizeof(s[i]),cmp),前面提到过。第一个参数可以看成是开始排序的地址。

二.快速排序的原理(简单介绍)

1.设置两个变量i,j,排序开始从i = 1, j = n;

2.将第一个元素作为关键数据,赋值给 x,即 x = a[1];

3. 从j开始向前搜索,即由后开始向前搜索,找到第一个小于 x的值,两者交换;

4.从i开始向后搜索,即由前开始往后搜索,找到第一个大于x的值,两者交换;

5,重复操作第3.4步,直到i=j;

示例:

int A[]={4, 3, 5, 7, 6, 1, 2};

 步骤:

1. x = 4, 

进行第一次交换:2,3,5,7,6,1,4;(j)

        第二次交换:2,3,4,7,6,1,5(i)

        第三次交换:2,3,1,7,6,4,5(j)

        第四次交换:2,3,1,4,6,7,5(i)

 第五次交换:i=j;结束一趟快速排序

现在以4为关键数据为中心,4前面的数据都要比4小,4后面的数据都要比4大。

然后再对这2组数据进行快速排序。

三.七种快速排序的代码实现

 

No.1.最简单的int型数组排序:

#include <stdio.h>
#include <stdlib.h>
int s[10000],n,i;		//可以不使用全局变量,直接在main()函数中定义
int cmp(const void *a, const void *b)
{
	return(*(int *)a-*(int *)b);
}
/*这里的(int *)a定义了一个指向int型的指针,注意int *两边的
括号不能少,然后(int *)a前面加上*就表示取其指向的值。这里
返回的是*(int *)a-*(int *)b,两个数相减的顺序跟函数形参顺序
一样这样就会将数组按升序排序,反之如果是return(*(int *)b-*(int *)a),
就会将数组按降序排列*/
void main()
{
	scanf("%d",&n);
	for(i=0;i<n;i++) 
		scanf("%d",&s[i]);
	qsort(s,n,sizeof(s[0]),cmp);	//传进去四个参数
	for(i=0;i<n;i++) 
		printf("%d\n",s[i]);
}

 

 

No.2.对double型数组排序

double型排序跟int原理是一样的,不过这里做个注释,本来是要判断如果a==b返回0的,但是严格来说,两个double(双精度)数是不可能相等的,只能说fabs(a-b)<1e-20之类的这样来判断,所以这里只返回了1和-1。

#include <stdio.h>
#include <stdlib.h>
double s[1000];
int i,n;
int cmp(const void * a, const void * b)
{
	return((*(double*)a-*(double*)b>0)?1:-1);
}
/*注意这里不能像上面对int型数组排序时那样直接返回*(double*)a-*(double*)b,
因为这个cmp函数的返回值已经规定了是int型,而*(double*)a-*(double*)b是double型,
这里是对这个double型数组进行了升序排列,如果return((*(double*)b-*(double*)a>0)?1:-1)
或者return((*(double*)a-*(double*)b>0)?-1:1)则对数组进行降序排列*/
void main()
{
	scanf("%d",&n);
	for(i=0;i<n;i++) 
		scanf("%lf",&s[i]);
	qsort(s,n,sizeof(s[0]),cmp);
	for(i=0;i<n;i++) 
		printf("%lf\n",s[i]);
}

 

 

No.3.对char字符型数组排序

/*原理跟int相同*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char s[10000],i,n;
int cmp(const void *a,const void *b)
{
	return(*(char *)a-*(char *)b);//这里直接把字符的ASCII码相减来比较字符的先后顺序
}
void main()
{
	scanf("%s",s);
	n=strlen(s);
	qsort(s,n,sizeof(s[0]),cmp);
	printf("%s",s);
}



 

No.4.对结构体的排序

#include <stdio.h>
#include <stdlib.h>
struct node
{
	double date;
	int flag;
} s[1000];
int i,n;
int cmp(const void *a,const void *b)
{
      return(((struct node *)a)->date > ((struct node *)b)->date?1:-1);
}
/*注意,这里的struct node *跟前面的int*,double*原理一样,都是一种指针类型,
这里是自己定义的一个指向结构体的指针类型,故写法为struct 结构体名称 *,
这里date是double型数据,故不可能有相等情况出现,只需返回1和-1即可,
这里即指的是用struct node里面的date作为关键字,进行排序,得到的结果是以date排序的,
这是按大到小排序,按小到大只须将1:-1换成-1:1就行了,看起来很方便的样子!*/

void main()
{
	scanf("%d",&n);
	for(i=0;i<n;i++)
	{
		s[i].flag=i+1;
		scanf("%lf",&s[i].date);
	}
	qsort(s,n,sizeof(s[0]),cmp);
	for(i=0;i<n;i++) 
		printf("%d %lf\n",s[i].flag,s[i].date);
}


 

No.5.对结构体二级排序

由于快排具有不稳定性,所以,需要加入flag标志记录先后顺序,来使其稳定(即data值相等的情况下按flag的值大小排序)。

#include <stdio.h>
#include <stdlib.h>
struct node
{
	double date;
	int flag;
} s[1000];
int i,n;
int cmp(const void *a,const void *b)
{
	if(((struct node *)a)->date !=( (struct node *)b)->date)
		return(((struct node *)a)->date > ((struct node *)b)->date?1:-1);
	else
		return(((struct node *)a)->flag - ((struct node *)b)->flag);
}
/*data值相等的情况下按flag的值大小排序,此时是升序排列,降序只需要将1:-1换成-1:1就行了,
按flag的降序只须将a,b的位置换一下就行了。*/
int main()
{
	scanf("%d",&n);
	for(i=0;i<n;i++)
	{
		s[i].flag=i+1; //flag记录了输入的先后顺序
		scanf("%lf",&s[i].date);
	}
	qsort(s,n,sizeof(s[0]),cmp);
	for(i=0;i<n;i++) 
		printf("%d %lf\n",s[i].flag,s[i].date);
}

或者结构体二级排序的另一种写法(补充):

#include <stdio.h>
#include<string.h>
#include <stdlib.h>
struct node
{
	double date;
	char name[20];
} s[1000];

int cmp(const void * a, const void * b)
{
	if ((*(struct node*)a).date> (*(struct node*)b).date)
		return -1;
	else if ((*(struct node*)a).date < (*(struct node*)b).date)
		return 1;
	else if (strcmp((*(struct node*)a).name,(*(struct node*)b).name) > 0)
		return 1;
	else if (strcmp((*(struct node*)a).name,(*(struct node*)b).name) < 0)
		return -1;
}
void main()
{
	int i,n;
	scanf("%d",&n);
	for(i=0;i<n;i++)
	{
		scanf("%lf",&s[i].date);
		scanf("%s",s[i].name);
	}
	qsort(s,n,sizeof(s[0]),cmp);
	for(i=0;i<n;i++) 
		printf("%lf %s\n",s[i].date,s[i].name);
}


 

No.6.对字符串数组的排序(char s[m][n]型)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char s[100][100];
int i,n;
int cmp(const void *a,const void *b)
{
 return(strcmp((char*)a,(char*)b));
}
/*这里调用了strcmp比较函数,这个函数根据字典序对字符串进行比较,按字符串的先后顺序依次返回1或0或-1*/
void main()
{
 scanf("%d",&n);
 for(i=0;i<n;i++) 
  scanf("%s",s[i]);
 qsort(s,n,sizeof(s[0]),cmp);
 for(i=0;i<n;i++) 
  printf("%s\n",s[i]);
}


 

No.7对指针数组排序(char *s[]型)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *s[100];//定义了一个指针数组,亦可称为指针的指针
int i,n;
int cmp(const void *a,const void *b)
{
	return(strcmp(*(char**)a,*(char**)b));//注意这里使用char**表示指针的指针
}
int main()
{
	scanf("%d",&n);
	for(i=0;i<n;i++)
	{
		s[i]=(char*)malloc(sizeof(char*));
		scanf("%s",s[i]);
	}
	qsort(s,n,sizeof(s[0]),cmp);
	for(i=0;i<n;i++) 
		printf("%s\n",s[i]);
}


好了,至此为止,快排的 基本以及代码实现我都已经写过了,由于是初学ACM的菜鸟,写博客只是想来提升自己,并且想把自己的小小经验总结出来分享,其中不免有错误以及借鉴别人的地方,还望读者海涵与更正!谢谢!


 

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值