当前位置:网站首页>The use of qsort function and its analog implementation
The use of qsort function and its analog implementation
2022-08-08 13:02:00 【Wild boar page`】
qsort 函数
函数功能
qsort 是C语言中基于快速排序思想的一种排序函数,与我们之前学过的冒泡排序不同,qsort 可以排序任意类型的数据(整形、浮点型、数组、结构体等等),同时,qsort 函数也是函数指针中回调函数应用的一个经典案例 .
函数参数
void qsort( void *base,
size_t num,
size_t width,
int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
# void* base:要排序数据的起始地址,把指针类型定义为void*,让qsort函数可以接收任何类型数据的地址;
# size_t num:要排序的元素个数;
# size_t width:每个元素的大小;
# __cdecl:函数调用约定;
# int (*compare)(const void *elem1, const void *elem2 )):函数指针,指向用于排序的函数
函数指针
假设我这里有一个名为 struct Stu 的结构体,里面有 name、age、height 三个成员变量,现在我们要调用 qsort 函数对多个这样的结构体变量进行排序,那么这里就会出现一个问题;
struct Stu 内部的排序依据有三个,分别是 name、age 和 height,我们即函数的调用者肯定是清楚我们想要以哪种依据来排序的,但是qsort 函数的实现者显然并不知道;
所以 qsort 函数中第四个参数是一个函数指针,该函数指针指向一个排序函数,该函数需要由 qsort 的调用者来提供,用于指定两个数据以何种方式进行比较.
int (*compare)(const void *elem1, const void *elem2 ));
# const void *elem1:用于比较的第一个数据;
# const void *elem2:用于比较的第二个数据;
排序函数的返回值
| -返回值 | -对应情况 |
|---|---|
| = 0 | 两个数据相等 |
| > 0 | 第一个数据大于第二个数据 |
| < 0 | 第一个数据小于第二个数据 |
函数使用
我们以上面提到的 struct Stu 结构体进行举例;
以 name 为依据进行排序:
#include <stdio.h>
#include <stdlib.h> //qsort 对应头文件
#include <string.h> //strcmp 对应头文件
struct Stu
{
char name[20];
int age;
int height;
};
int sort_by_name(const void* e1, const void* e2) //排序函数
{
//由于e1 e2 是void*类型,不能直接解引用,所以我们需要先将其强转为 struct Stu* 类型,然后再找出其中的 name 进行比较
//由于strcmp函数和排序函数的返回值相同,且name是字符串,所以这里我们直接调用strcmp函数
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int main()
{
struct Stu stu[3] = {
{
"zhangsan", 23, 185}, {
"lisi", 20, 180}, {
"wangwu", 25, 186} };
qsort(stu, 3, sizeof(struct Stu), sort_by_name);
int i = 0;
for (i = 0; i < 3; i++)
{
printf("姓名:%s\t年龄:%d\t身高:%d\n", stu[i].name, stu[i].age, stu[i].height);
}
return 0;
}

以 age 为依据进行比较:
#include <stdio.h>
#include <stdlib.h> //qsort 对应头文件
struct Stu
{
char name[20];
int age;
int height;
};
int sort_by_age(const void* e1, const void* e2) //排序函数
{
//由于e1 e2 是void*类型,不能直接解引用,所以我们需要先将其强转为 struct Stu* 类型,然后再找出其中的 age 进行比较
//根据排序函数的返回值要求,我们直接返回二者的差值即可
return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
int main()
{
struct Stu stu[3] = {
{
"zhangsan", 23, 185}, {
"lisi", 20, 180}, {
"wangwu", 25, 186} };
qsort(stu, 3, sizeof(struct Stu), sort_by_age);
int i = 0;
for (i = 0; i < 3; i++)
{
printf("姓名:%s\t年龄:%d\t身高:%d\n", stu[i].name, stu[i].age, stu[i].height);
}
return 0;
}

以 height 为依据进行比较:
#include <stdio.h>
#include <stdlib.h> //qsort 对应头文件
struct Stu
{
char name[20];
int age;
int height;
};
int sort_by_height(const void* e1, const void* e2) //排序函数
{
//由于e1 e2 是void*类型,不能直接解引用,所以我们需要先将其强转为 struct Stu* 类型,然后再找出其中的height进行比较
//根据排序函数的返回值要求,我们直接返回二者的差值即可
return (((struct Stu*)e1)->height - ((struct Stu*)e2)->height);
}
int main()
{
struct Stu stu[3] = {
{
"zhangsan", 23, 185}, {
"lisi", 20, 180}, {
"wangwu", 25, 186} };
qsort(stu, 3, sizeof(struct Stu), sort_by_height);
int i = 0;
for (i = 0; i < 3; i++)
{
printf("姓名:%s\t年龄:%d\t身高:%d\n", stu[i].name, stu[i].age, stu[i].height);
}
return 0;
}

qsort 函数的模拟实现
我们之前学习了冒泡排序,我们知道,冒泡排序只能排序整形的数据;今天我们就用快速排序的思想来对冒泡排序进行改造,让它能够达到和 qsort 函数同样的效果,可以排序任意类型的数据.
冒泡排序
首先我们先来写一个普通的冒泡排序:
void bubble_sort(int arr[], int n)
{
int i = 0;
int j = 0;
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = {
1,2,4,5,8,3,6,7,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz); //冒泡排序
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

现在我们来对冒泡排序进行改造:
首先是参数的设置,为了达到和 qsort 函数同样的效果,我们这里参数和 qsort 设置为一样;然后是代具体实现,冒泡排序的整体框架我们不用改变,要改变的地方只是元素进行比较和交换的方法.
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++) //将两个元素每一对字节的内容进行交换
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - i - 1; j++)
{
//由于base是void*的,所以不能直接对其进行+-整数的操作
//同时又为了能够操作任意类型的数据,我们把base强转为最小数据类型的大小:char*
//回调函数:使用排序函数的返回值判断是否要进行元素的交换
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//如果前一个元素>后一个元素,就将两个元素的数据进行交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
现在我们用改造后的冒泡排序来对我们上面的结构体进行排序,检验代码的正确性:
struct Stu
{
char name[20];
int age;
int height;
};
int sort_by_name(const void* e1, const void* e2) //排序函数:按姓名
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int sort_by_age(const void* e1, const void* e2) //排序函数:按年龄
{
return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
int sort_by_height(const void* e1, const void* e2) //排序函数:按身高
{
return (((struct Stu*)e1)->height - ((struct Stu*)e2)->height);
}
int main()
{
struct Stu stu[3] = {
{
"zhangsan", 23, 185}, {
"lisi", 20, 180}, {
"wangwu", 25, 186} };
int i = 0;
bubble_sort(stu, 3, sizeof(struct Stu), sort_by_name);
printf("以姓名排序:\n");
for (i = 0; i < 3; i++)
{
printf("姓名:%s\t年龄:%d\t身高:%d\n", stu[i].name, stu[i].age, stu[i].height);
}
printf("----------------------\n");
bubble_sort(stu, 3, sizeof(struct Stu), sort_by_age);
printf("以年龄排序:\n");
for (i = 0; i < 3; i++)
{
printf("姓名:%s\t年龄:%d\t身高:%d\n", stu[i].name, stu[i].age, stu[i].height);
}
printf("----------------------\n");
bubble_sort(stu, 3, sizeof(struct Stu), sort_by_height);
printf("以体重排序:\n");
for (i = 0; i < 3; i++)
{
printf("姓名:%s\t年龄:%d\t身高:%d\n", stu[i].name, stu[i].age, stu[i].height);
}
return 0;
}

我们上面只是用冒泡排序来模拟实现了 qsort 函数的功能,并不是说 qsort 函数的内部也是用冒泡排序实现的,这样做明显有些得不偿失,因为冒泡排序的时间复杂度是比较高的;但是它们都能达到一样的效果,并且都是基于快速排序的思想来设计的.
边栏推荐
- 硬盘数据恢复工具
- 你的 golang 程序正在悄悄内存泄漏
- 八月粉丝福利来了!大疆手机云台你爱了吗?
- 老手也常误用!详解 Go channel 内存泄漏问题
- 海外邮件发送指南(一)
- 作为一个年薪50W阿里P7架构师的必备知识:并发+JVM+多线程+Netty+MySQL
- 北京 北京超大旧货二手市场开集了,上千种产品随便选,来的人还真不少
- day01 - Introduction to Web API - Introduction to DOM - Getting Elements - Event Basics - Manipulating Elements - Exclusive Operations - Custom Attribute Operations - Node Operations - Cases: Dynamica
- 《show your work》 从现在开始!
- 详解轮播图二-通过left定位来轮播图片
猜你喜欢

office安装出现了“office对安装源的访问被拒绝30068-4(5)”错误

【C语言】动态内存管理

neural network classification

安科瑞预付费水电集团物业解决方案-Susie 周

Kunpeng Developer Creation Day 2022: Kunpeng Full-Stack Innovation and Developers Build Digital Hunan

期货开户的交易通道和后续服务

ctfshow 七夕杯(复现)

鲲鹏开发者创享日2022:鲲鹏全栈创新 与开发者共建数字湖南

【C语言】文件相关操作

分享面试阿里、京东、网易等大厂后的面经及面试心得,让你秋招不再害怕
随机推荐
Acwing3452. 进制转换
PM2 入门(二)
爱可可AI前沿推介(8.8)
Promise 解决阻塞式同步,将异步变为同步
#yyds Dry Goods Inventory#【Yugong Series】August 2022 Go Teaching Course 005-Variable
学习与尝试 --&gt; 事件风暴
(原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化
MySQL Dual-Master 双向同步
一文读懂配置管理(CM)
vim /etc/profile 写入时 出现 E121:无法打开并写入文件解决方案
DDoS攻击为什么是无解的
#yyds干货盘点#【愚公系列】2022年08月 Go教学课程 005-变量
安科瑞预付费水电集团物业解决方案-Susie 周
Jenkins - 持续集成介绍(1)
老手也常误用!详解 Go channel 内存泄漏问题
十年架构五年生活-08 第一次背锅
迁移学习(Transfer Learning)的背景、历史及学习课
模式识别 学习笔记:第六章 其他分类方法 (持续更新中。。。)
ReentrantLock原理,ReentrantLock和synchronized区别
一名合格的程序员是如何优雅地解决线上问题的?