我们曾经知道C语言排序有冒泡排序函数,选择排序函数等等,但这些自定义函数都不够普适,只能针对某类型的数据进行排序。C语言中有没有什么函数能够实现呢??
通过指针进阶我们了解到了回调函数,了解到了函数指针,那么现在,我们就可以来学习一种新的快速排序函数qsort了。这类函数对所有数据都适用,非常牛逼的一种函数!那么本篇文章将介绍C语言中快速排序函数qsort。通过简洁高效的代码,提供一个清晰明了的讲解,让我们开始吧!
通过以往的学习我们可以很轻松的写出冒泡排序,代码如下:
#include
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void bubble_sort1(int arr[], int sz)//整型数据的冒泡排序函数
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void test1()
{
int arr[] = {9,8,7,6,5,4,3,2,1,0};
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr,sz);
bubble_sort1(arr, sz);
print_arr(arr, sz);
}
int main()
{
test1();//用传统冒泡实现排序
return 0;
}
但是很可惜只能针对某类型的数据进行排序,实在是有点鸡肋,既然qsort也是排序函数,那么接下来让我们了解一下它有什么独特的地方,试试能不能在冒泡排序的基础上进行优化,让我们一起看看吧~ =v=
这里先分享一个网站:cplusplus。
这个网站虽然看样子是C++的网站,但是也可以搜索有关C语言的相关函数知识。
不过要注意的是这个网站现在有新版,取消了搜索栏,如果是初学者建议找到这个按键进入旧版网站就可以进行搜索了,耶~
好的好的!我们回到正题上来,经过我们的一顿搜索操作,找到了qsort函数的定义:
看这么多英文头简直要爆了,实际上面这么大一堆文字直观有效的是红色的那一块,我们看这一部分就足够了。通过观察发现它的函数返回类型是void,函数参数有base,num,size和compar这一大堆参数,接下来我们将逐个进行讲解。Let‘s GO!
首先要说明一下,我们这里并不会具体深入展开qsort函数中的具体代码,我们只需了解它的函数逻辑,这样就已经足够我们进行实际的运用啦
注意使用qsort时需要带上头文件:#include
void* base //指向目标排序数组的首元素的指针(地址)
首元素地址base,它是指向目标排序数组的首元素的指针(地址),来确定具体排序的起点。
拿上面冒泡排序举例,此时如果想要排序,base所指就如图所示:
我们可以发现它的指针类型是void*,非常的耐人寻味,这里来讲讲它的奥妙。
void* 是无类型指针,他的作用是万金油,可以把任意元素类型的地址放入该指针中。如果要再使用就将其再强制转换为原类型即可。
你发现了吗?没错!!!!
这就意味着因为它能使一段代码适用所有元素类型的想法成为可能!!!
我们之后还会经常遇到void*,就在这里进行统一的讲解了。
size_t num //目标排序数组的元素个数
元素个数num,它是指目标排序数组的元素个数,就如同我们前文冒泡排序中数组的sz一样,他来确定我们排序数据的个数。
size_t 类型: 无符号整型。
size_t size //目标排序数组元素大小(字节,sizeof(arr[0]))
元素大小size,它里面存放的是目标排序数组一个元素的大小,单位是字节,具体计算也很简单,通过sizeof(arr[0])即可。(目标数组假设为arr)
int (*compar)(const void*,const void*)
//对应数组元素类型的元素比较函数(自行编写)
这个参数我们可以发现是个函数指针,该指针所指向的函数是需要我们去自定义编写的,这个就要根据所排序的数据类型来进行具体的选择了。
同时我们耶不难发现,这个函数指针所指向的函数的形式参数类型也是void*指针。这就很奇怪了,这样的数据具体应该怎么比较呢?
我们可以再来看看C程序编译库中的解释:
通过图片我们得知,这个自定义比较函数是通过传进来的两个指针来比较的,返回类型是整型,通过返回的整数表达出两者的大小关系。
如果 *p1> *p2 ,那么就返回大于0的值
如果 *p1= *p2,那么就返回0
如果 *p1< *p2 ,那么就返回小于0的值
根据上面的分析,我们现在可以尝试着将我们的冒泡排序进行一个优化了,我们还是先进行一个整型数据的排序。具体就是三点,参数的更新,比较函数和swap函数的编写。
将需要的形式参数都更新为刚刚所学内容,代码如下:
#include
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void bubble_sort2(void* base, size_t num,size_t size,int(*p)(const void*,const void*))//改良版
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (比较函数)
{
swap函数;
}
}
}
}
void test2()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
char size = sizeof(arr[0]);
print_arr(arr, sz);
bubble_sort2(arr, sz,size,cmp_int);
print_arr(arr, sz);
}
整体上和冒泡函数差距并不大,接下来就是比较函数。
通过上文我们可以知道是通过返回值的大小来判断二者的大小关系的,那么我们可以通过 返回两者相减的值来恰好实现要求,只需要判断是否>0即可,代码如下:
int cmp_int(const void* e1, const void* e2)//bubble_sort2的比较函数
{
return *(int*)e1 - *(int*)e2;//使用void*指针注意强制类型转换
}
前面我们有说过,size的类型是无符号整型,存放的内容是目标排序数组的一个元素的字节大小。
为什么是字节呢?
我们知道,如果我们要排序整型数组,就知道它的元素是四个字节。但是我们这个模拟qsort是为了给任意数据类型数组排序的,而每种类型的元素大小不同,有可能不够规则。比如整型4个字节,而有的结构体数据24个字节,为了保证普适化,我们就可以通过字节这个基本单位来进行交换。
换个说法讲,我们知道整型数据是四个字节,如果交换两个整形数据就将四个字节整块交换,如图所示:
而如果单位是字节的话,就可以不用考虑目标排序数组数据类型了,最终都是将元素整体交换,达到同样的效果。
明白了原理,就来实践感受一下!代码如下:
void swap(char* pa,char* pb,size_t size)//bubble_sort2的交换函数
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *pa;
*pa = *pb;
*pb = tmp;
pa++;
pb++;
}
}
最后汇总起来,用冒泡排序模拟实现qsort函数代码如下:
#include
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)//bubble_sort2的比较函数
{
return *(int*)e1 - *(int*)e2;
}
void swap(char* pa,char* pb,size_t size)//bubble_sort2的交换函数
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *pa;
*pa = *pb;
*pb = tmp;
pa++;
pb++;
}
}
void bubble_sort2(void* base, size_t num,size_t size,int(*p)(const void*,const void*))//改良版
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (p((char*)base+j * size,(char*)base + (j + 1) * size)>0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
}
}
}
}
void test2()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
char size = sizeof(arr[0]);
print_arr(arr, sz);
bubble_sort2(arr, sz,size,cmp_int);
print_arr(arr, sz);
}
int main()
{
//test1();//用传统冒泡实现排序
//test2();//了解qsort原理后更新冒泡实现排序,整型数组排序(变得通用,能面向各种类型的数据)
printf("\n别忘了点赞三连支持欧o(>ω< )o!!!\n");
printf("(深情)\n");
return 0;
}
知道了一种,我们就可以举一反三了,只需要将结构体的比较函数换上就可以排序结构体了。
注意!!!结构体数据排序时,如果是根据字符来排序就不能用相减来比较了。我们需要运用strcmp函数来进行比较排序,该函数返回值同样符合qsort判断规律。(运用注意引用头文件#include
)
代码如下:
typedef struct student
{
char name[20];
int age;
}Student;
int cmp_by_age(const void* e1, const void* e2)//通过年龄排序
{
return ((Student*)e1)->age - ((Student*)e2)->age;
}
void test3()
{
Student arr[3] = { {"zhangsan",200},{"lisi",88},{"wangwu",10}};
int size = sizeof(arr[0]);
int sz = sizeof(arr) / size;
bubble_sort2(arr, sz, size, cmp_by_age);
}
int main()
{
//test3();//结构体数组排序
//test4();//运用qsort实现排序
printf("\n别忘了点赞三连支持欧o(>ω< )o!!!\n");
printf("(深情)\n");
return 0;
}
代码运行结果如下:
可以明显看到通过年龄排序最小的王五跑到了数组的最前面,成功排序!
其他数据也可以进行尝试,我这里就不拓展哩。^^
明白了qsort定义和实现逻辑,也通过理论对冒泡排序进行了优化更新,现在来调用使用qsort那不是轻轻松松?别忘了引头文件哦!我们来排排整型数组试试。代码如下:
#include
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)//int的比较函数
{
return *(int*)e1 - *(int*)e2;
}
#include
void test4()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
char size = sizeof(arr[0]);
print_arr(arr, sz);
qsort(arr, sz, size, cmp_int);
print_arr(arr, sz);
}
int main()
{
test4();//运用qsort实现排序
printf("\n别忘了点赞三连支持欧o(>ω< )o!!!\n");
printf("(深情)\n");
return 0;
}
好了以上就是本篇“【C语言】模拟实现与解析快速排序函数qsort”博客的全部内容啦,感谢各位的阅读=v=,如有不足之处欢迎在评论区指出哦!!
觉得可以的话别忘了点赞三连支持一下欧!拜托啦这对我真的很重要o(>ω< )o!!!