本文详细讲解qsort函数用法,并包含很多知识细节,干活满满!
qsort函数功能
排序是一个处理数据常用的功能,qsort(quick sort)快速排序就是八大排序算法之一,时间复杂度O(n)=nlogn。
qsort使用需要包含头文件<stdlib.h>,让qsort快排函数出彩的不只是它的排序速度,更是它几乎可以排序所有类型数组, 整型、字符型、浮点型,甚至根据结构体某个成员排序,不论升序降序, 都可以轻松实现。
接下来是qsort的用法,以及qsort是如何通过它的参数实现排序。
qsort函数声明
void qsort( void *base,
size_t num,
size_t width,
int (*cmp )(const void *elem1, const void *elem2 ) );
上面代码是qsort库函数的声明:
- 函数是void类型,没有返回值。
- base是一个无类型指针,用来接收要被排序的数组首元素地址。void*可以指向任何类型的数据, 从函数参数我们就可以看出,qsort几乎可以排序所有类型。但是对于void*类型指针,我们要注意到一点:
void*类型的指针无法访问地址数据,这是因为指针压根就不知道它要访问多大空间,那么即使能访问得到的数据也毫无意义。因此不能对void*类型指针解引用操作,也不能做地址偏移±操作,这是语法型错误。
- num是size_t类型,用来接收数组要被排序的元素个数, 大家可以记住这个类型,**size_t是库用typedef对unsigned int类型命名的新名字,**所以num就是无符号整型。
- width也是size_t类型,用来接收数组一个元素的字节大小。
- cmp则是一个函数指针,这个函数返回值是int,有两个const修饰的void*型参数, 这个函数是由我们自己编写的,不过非常简单很容易实现。这也是qsort能排序很多类型的原因之一——它很灵活。 有的读者可能不清楚什么是函数指针,下面简要介绍一下。
函数指针
函数是如何被调用的呢,函数也有为它自己在栈区开辟的空间, 理所当然一个函数也有它自身的地址,就连main函数也不例外。
那么有了该函数地址我们就可以调用,或者传递该函数,也有了对应的函数指针来指向函数地址。 函数指针的类型当然是对应函数类型(包括返回类型和参数),下面举个简单例子:
#include <stdio.h>
//我们定义一个简单的返回较大值函数
int max(int a, int b)
{
return a > b ? a : b;
}
int main()
{
//函数名和数组名相似都代表地址
//因为()的优先级高于*,所以要先把*和pf括起来,声明他是一个指针
int (*pf)(int , int )=max;//也可以写成int(*pf)(int a,int b)=&max;
//用max直接调用
int c = max(10, 20);
printf("max传递10,20后的c:%d\n", c);
//用函数指针pf调用
c = pf(10, 30);
printf("pf传递10,30后的c:%d\n", c);
return 0;
}
qsort函数用法
qsort函数声明讲解完了,下面就是如何使用了。废话不多说直接上代码:
注意:cmp函数返回值大于0交换,小于等于0都不交换。
整型
#include <stdio.h>
int cmp(const void*e1,const void*e2)
{
//因为无类型无法解引用,我们要根据需求强制类型转化,再解引用
//e1是前一个元素,e2是后一个元素,返回值大于0交换,下面实现的是升序排序
return *(int*)e1-*(int*)e2;//前比后大交换
//如果要实现降序,从大到小,就应该写成
//return *(int*)e2-*(int *)e1;后比前大交换
}
int main()
{
int arr[10]={2,3,10,9,1,7,5,6,8,4};
//这里我们想把arr升序排序,也就是从大到小排序
//第一个参数是首元素地址,一般传的都是数组名
//第二个参数是需要排序元素个数,一般直接填写,或借助sizeof计算
//第三个参数是一个元素大小,直接用sizeof(arr[0])计算
//第四个参数是我们编写的比较函数地址,注意此函数返回类型和参数类型是固定的,不能更改。
qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),cmp);
for(int i=0;i<10;i++)
{
printf("%d ",arr[i]);
}
return 0;
}
浮点型
对于不同类型的排序,主要是我们自己编写的cmp比较函数的修改:
//float类型升序排序
int cmp(const void*e1,const void *e2)
{
//为了防止差值是零点几几,导致返回值是0,我们这样处理
if((*(float*)e1-*(float*)e2)>0)
{
return 1;
}
//其它情况都不交换,我们直接返回0
return 0;
}
//double类型升序排序
int cmp(const void*e1,const void *e2)
{
if((*(double*)e1-*(double*)e2)>0)
{
return 1;
}
return 0;
}
字符型
想必通过上述两种类型举例,你已经可以写出字符型的cmp比较函数了。
int cmp(const void*e1,const void *e2)
{
//字符类型升序排序
//字符类型是特殊的整型,ASCII码值就是它的大小,可以直接作差
return *(char*)e1-*(char*)e2;
}
字符串型
字符串类型比较大小不能直接作差,要借助strcmp函数根据字典序比较大小。
int cmp(const void*e1,const void *e2)
{
//strcmp两个参数就是字符型指针,直接强制转换传参即可,
//前大于后会返回值1,前等于后返回值0,前小于后返回值-1。
return strcmp((char*)e1,(char*)e2);
}
结构体型
结构体排序看似很难,实际上理解清楚qsort用法十分简单。只需要把元素强制转换成结构体类型,再根据结构体某成员排序即可。
我们就拿学生举例,上完整代码:
#include <stdio.h>
#include <string.h>
struct student
{
//我们要分别根据学生的名字升序,分数降序排序
char name[20];
int score;
};
//名字升序排序函数
int name_cmp(const void *e1, const void *e2)
{
//把元素强制转换成结构体类型,再根据结构体某成员排序
return strcmp(((student *)e1)->name, ((student *)e2)->name);
}
//分数降序排序函数
int score_cmp(const void *e1, const void *e2)
{
return ((student*)e2)->score-((student*)e1)->score;
}
int main()
{
struct student arr[3]={{"zhangsan",80},{"lisi",92},{"wangwu",99}};
//根据名字升序排序
qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),name_cmp);
printf("名字升序排序后:\n");
for(int i=0;i<3;i++)
{
printf("%s %d\n",arr[i].name,arr[i].score);
}
printf("\n");
//根据分数降序排序
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), score_cmp);
printf("分数降序排序后:\n");
for(int i=0;i<3;i++)
{
printf("%s %d\n",arr[i].name,arr[i].score);
}
return 0;
}
my_qsort函数模拟实现
学习完上面,你已经势不可挡了,大部分类型数组排序都可以轻松搞定。
下面我们更深入一步,用冒泡排序的方式,观察这些参数是如何巧妙地完成排序的:
void swap(char *str1, char *str2, int width)
{
char tmp = 0;
//对应交换一个元素大小width字节内容
for (int i = 0; i < width; i++)
{
tmp = *(str1 + i);
*(str1 + i) = *(str2 + i);
*(str2 + i) = tmp;
}
}
void my_qsort(void *arr, int num, int width, int (*cmp)(const void *, const void *))
{
//冒泡排序框架
for (int i = 0; i < num - 1; i++)
{
for (int j = 0; j < num - i - 1; j++)
{
//我们想比较两个元素大小,只需要把每个元素的首字节地址传递过去即可
//cmp会帮我们实现类型转换比较,注意每个地址的偏移量不要写错
if (cmp((char *)arr + j * width, (char *)arr + (j + 1) * width) > 0)
{
//满足交换条件了,那么只需要将这两个width大的空间内容对应交换
//我们把交换独立封装为函数
swap((char *)arr + j * width, (char *)arr + (j + 1) * width, width);
}
}
}
}
int int_cmp(const void *e1, const void *e2)
{
return *(int *)e1 - *(int *)e2;
}
int char_cmp(const void *e1, const void *e2)
{
return *(char *)e1 - *(char *)e2;
}
int main()
{
int darr[10] = { 1,2,4,6,9,10,3,8,7,5 };
char carr[10+1] = "ahkgdefzpx";
my_qsort(darr, 10, sizeof(int), int_cmp);
my_qsort(carr, 10, sizeof(char), char_cmp);
for (int i = 0; i < 10; i++)
{
printf("%d ",darr[i]);
}
printf("\n");
printf("%s", carr);
return 0;
}
如果你能理解,恭喜你真的学会了个很NB的东西!
码字不容易,欢迎关注、点赞、收藏、评论、转发。