C语言--模拟实现库函数qsort

news2024/11/29 19:08:26

什么是qsort

qsort是一个库函数,是用来排序的库函数,使用的是快速排序的方法(quicksort)
qsort的好处在于:
1,现成的
2,可以排序任意类型的数据

在之前我们已经学过一种排序方法:冒泡排序。排序的原理是两两相邻的元素进行比较。但是冒泡排序的缺陷就在于只能两两整型进行比较,可现实生活中很多东西的比较并不只是仅仅局限于数字的比较,比如名字的排序等等。
但是qsort就可以排序任意类型的数据
1,比较两个整数的大小(< > =)
2,比较两个字符串(strcmp)
3,比较两个结构体数据(学生:张三,李四,王五)

qsort的声明与参数

我们先来看C标准库里对qsort函数的描述

头文件

qsort需引用的头文件为 <stdlib.h>

#include <stdio.h>

声明

qsort函数的声明如下

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
{
}

参数

qsort函数的参数如下

base -- 指向了待排序数组的第一个元素
num -- 待排序的元素个数
size -- 每个元素的大小,单位是字节
cmp -- 指向一个函数,这个函数可以比较两个元素的大小

书写格式

我们以给一个数组按升序排序为例,看下面这段代码:

#include <stdio.h>
#include <stdlib.h>


int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d", arr[i]);
	}

}
void test1()
{
	int arr[] = { 9,8,6,7,5,4,2,3,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}
int main()
{
	test1();
	return 0;
}

值得注意的是,其中的cmp_int函数是系统已经封装好了的一个函数,但是因为编译者在编译这个函数的时候,并不知道后来的人们使用这个qsort去排序什么类型的元素,所以用了void* 这样一个通用类型的指针
这就意味着void*这个指针可以存放任意数据类型的指针。

int main()
{
	int a = 0;
	int* p = &a;//通常写法
	char* p = &a;//编译器会报错,char*类型与a类型不匹配
	void* p = &a;//通用指针
	*p;//报错
	*(int*)p;//正确
	return 0;
}

为什么用void*指针来存放地址直接解引用会报错呢?这是因为系统在解引用操作时并不知道要访问几个字节,必须要强制类型转换才能正常解引用

测试qsort排序结构体

学习完了qsort函数的基本格式,我们现在来尝试运用一下它,以实现对结构体的排序为例:

以年龄升序排序

struct Stu
{
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()
{

	struct Stu s[] = { {"zhangsan",20},{"lisi",25},{"wangwu",15} };
	int sz = sizeof(s) / sizeof(s[0]);
	//按年龄来比较
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
	test2();
	return 0;
}

我们用监视的方法来看一下排序的结果
图1

以姓名升序排序

如果我们要以姓名排序,要注意的是,姓名是一个字符串,而字符串是不能直接相减的,所以我们要使用另一个库函数—strcmp来进行比较,但是返回值类型仍然为整型,因为strcmp这个库函数的返回值类型就是整型,代码书写如下

struct Stu
{
	char name[20];
	int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}
void test2()
{

	struct Stu s[] = { {"zhangsan",20},{"lisi",25},{"wangwu",15} };
	int sz = sizeof(s) / sizeof(s[0]);
	//按姓氏来比较
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
	//test1();
	test2();
	return 0;
}

我们同样用监视的方法来看一下结果
图2
结果也是成功以姓名升序排列。

用冒泡排序来模拟实现

因为我们还没有学习快速排序的底层逻辑,所以本章我们先用冒泡排序的思想来实现一个类似于qsort这个函数功能的冒泡排序函数bubble_sort,效果是一样的。
在之前的文章里有专门的对于冒泡排序实现过程的讲解,这里不再过多赘述,直接上代码

void bubble_sort(int arr[], int sz)
{
   int i = 0;

   //冒泡排序的趟数
   for (i = 0; i < sz - 1; i++)
   {
   	//一趟冒泡排序的过程
   	int j = 0;
   	for (j = 0; j < sz - 1 - i; j++)
   	{
   		if (arr[j] > arr[j + 1])
   		{
   			int temp = arr[j];
   			arr[j] = arr[j + 1];
   			arr[j + 1] = temp;
   		}
   	}
   }
}
int main()
{
   int arr[] = { 3,1,5,9,2,4,7,6,8,0 };
   //排序 - 升序
   //冒泡排序
   int sz = sizeof(arr) / sizeof(arr[0]);

   bubble_sort(arr,sz);//arr是数组首元素的地址

   int i = 0;
   for (i = 0; i < sz; i++)
   {
   	printf("%d ", arr[i]);
   }

   return 0;
}

我们先来看以冒泡排序为原理实现的快排源码再逐一分析:

int cmp_int(const void* p1, const void* p2)
{
   return *(int*)p1 - *(int*)p2;
}

void Swap(char* buf1, char* buf2,int 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* p1, const void* p2))
{
   int i = 0;
   for (i = 0; i < num - 1; i++)
   {
   	int j = 0;
   	for (j = 0; j < num - 1 - i; j++)
   	{
   		//俩相邻元素比较
   		//假设以升序排列
   		if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
   		{
   			Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
   		}

   	}
   }
}
void test3()
{
   int arr[] = { 3,2,1,4,5,7,8,9,6 };
   int sz = sizeof(arr) / sizeof(arr[0]);
   bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
   print(arr, sz);
}
int main()
{
   test3();
   return 0;
}

图解

我们来通过图文结合·进一步了解各部分的作用:
图3
通过图解应该能对该算法有更深一步的理解。

以上就是关于模拟实现库函数qsort(快排)的全部内容了,如有出入,欢迎指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/383348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设置CentOS7的时间与网络同步

1.设置时区为北京时间 [rootlocalhost ~]# timedatectl set-timezone Asia/Shanghai 2.查看系统时间 [rootlocalhost ~]# timedatectl Local time: 四 2023-03-02 17:40:41 CST #系统时间 Universal time: 四 2023-03-02 09:40:41 UTC …

安卓APK打包流程浅析

在面试中&#xff0c;大公司会问你......APK打包流程全过程&#xff1f;APK签名在实际业务中能处理什么&#xff1f;APK加固原理是如何实现的&#xff0c;怎么保证安全&#xff1f;APK签名V1,V2,V3的区别于原理&#xff0c;有什么漏洞&#xff1f;这里只讲解打包流程的全过程。…

Phind-面向程序员的AI聊天对话机器人

ChatGPT在国内没开放&#xff0c;很多人注册不了。导致了很多人走illegal渠道获取账号密码。这样是不对的。 今天介绍一款面向程序员的ai聊天机器人Phind&#xff0c;ta目前可以不用注册直接使用、免费、也不用梯子。 &#xff08;且用且珍惜&#xff0c;不知道之后会不会跟Cop…

Makefile的使用

Makefile的使用 自动化编译脚本&#xff0c;这个东西就是&#xff0c;进行简单的设置&#xff0c;然后实现原码编成为相应程序&#xff0c;简单化自己进行相关操作的过程。不需要一个个自己进行全部进行输入。而且还有许多的简化书写方法。 ​ 这个Makefile的本质为一种脚本语言…

SYSU程设c++(第二周) string、函数重载、constexpr、auto

创建string对象&#xff1a; string s1; // s1 未使用初始化参数&#xff0c;即默认初始化为空字符串 string s2 "c" ; //不是赋值运算&#xff0c;它等价于 string s2("c") ,是初始化 string s3 (5, s); // s4 初始化为 5 个 s&#xff1b; string…

可视化图表之奥妙——百分比堆叠柱状图

百分比堆叠柱状图是属于堆叠柱状图的一种&#xff0c;是指将每个柱子进行分割以显示相同类型下各个数据的占比大小情况。百分比堆叠柱形图上柱子的各个层代表的是该类别数据占该分组总体数据的百分比&#xff0c;但不适用于对比不同分组内同个分类的数据大小或者对比各分组总数…

企业邮箱对企业有哪些好处以及便捷性

企业邮箱拥有更专业的办公功能&#xff0c;更适合职场使用。同时&#xff0c;使用企业邮箱还可以帮助企业“公私分明”。一方面保护了公司信息&#xff0c;另一方面也提高了工作效率。加上公司统一邮箱也有助于提升公司形象。使用企业邮箱除了收发邮件方便外&#xff0c;还可以…

MyBatis——配置文件完成增删改查

1.首先先创建一个新的表,使用下面的sql语句 -- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand (-- id 主键id int primary key auto_increment,-- 品牌名称brand_name varchar(20),-- 企业名称company_name varchar(20…

openpnp - configure - 丢弃(Discard)位置的设置

文章目录openpnp - configure - 丢弃(Discard)位置的设置概述笔记设置丢弃位置吸取元件失败后, 吸嘴一直吸气的处理ENDopenpnp - configure - 丢弃(Discard)位置的设置 概述 测试时, 吸取了一个元件, 吸取成功了, 现在想将这个料丢掉. 点击控制面板-Special页中的Discard不好…

WSN_1 介绍;部分应用介绍

学习自书籍&#xff1a;Fundamentals of Wireless Sensor Networks. WSN 介绍 传感器 从基础角度说&#xff0c;传感器观测采集现实世界的一些数据。 另一个名称是 transducer 换能器&#xff0c;指传感器将一些形式的信号转换为其他形式的信号&#xff0c;如光敏传感器 光…

华为OD机试用Python实现 -【集五福】 |老题且简单

华为OD机试题 最近更新的博客华为 OD 机试 300 题大纲集五福题目描述输入描述输出描述示例一输入输出示例二输入输出代码编写思路Python 代码最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典

进程和线程理论知识

1.进程和线程之间的联系。 进程是程序依次执行的过程&#xff0c;线程是比进程小的执行单位。 一个进程在其执行过程中可以创建多个线程。 多个线程共享进程的堆和方法区内存资源。 进程是OS进行资源分配的基本单位。 线程是OS进行调度的基本单位。 进程和线程是1&#xff1…

GPU编程实战1

给一张测试图&#xff0c;对测试图分别cpu和GPU进行处理&#xff0c;进行时间统计&#xff0c;最后做展示。 环境&#xff1a;win10 cuda11.3 python3.7 numba 等 硬件&#xff1a;cpu:i59400 ,gpu:RTX1650 4G 首先进行cuda安装&#xff0c;cuDNN等的安装&#xff0c;参考该…

和年薪30W的阿里测开工程师聊过后,才知道我的工作就是打杂的...

前几天和一个朋友聊面试&#xff0c;他说上个月同时拿到了腾讯和阿里的offer&#xff0c;最后选择了阿里。 阿里内部将员工一共分为了14个等级&#xff0c;P6是资深工程师&#xff0c;P7是技术专家。 其中P6和P7就是一个分水岭了&#xff0c;P6是最接近P7的不持股员工&#x…

720°VR全景家装设计,为传统行业注入新生命力

导语&#xff1a;VR全景家装是一种基于虚拟现实技术的新型家居装修方案&#xff0c;可以通过虚拟现实技术让用户更真实地体验家居装修效果&#xff0c;避免了传统装修中的繁琐流程和不可预知的风险。近几年来&#xff0c;VR全景装修盛行&#xff0c;打破传统二维空间模式&#…

【ArcGIS学习记录01】--利用CRU TS数据集绘制降雨量分布图

【ArcGIS学习记录01】–利用CRU TS数据集绘制降雨量分布图 注&#xff1a;仅作为本人的学习记录方便以后复习查阅。 一、介绍 CRU TS 是目前使用最广泛的气候数据集之一&#xff0c;由英国国家大气科学中心 (NCAS) 制作。简而言之我们能在CRU TS官网上获得几乎各个研究领域能…

[Java基础]—JDBC

前言 其实学Mybatis前就该学了&#xff0c;但是寻思目前主流框架都是用mybatis和mybatis-plus就没再去看&#xff0c;结果在代码审计中遇到了很多cms是使用jdbc的因此还是再学一下吧。 第一个JDBC程序 sql文件 INSERT INTO users(id, NAME, PASSWORD, email, birthday) VAL…

LeetCode 2 - 两数相加

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 开…

常用基础硬件知识 - 判断MOS管导通

目录1. 概述2. 判断MOS管的导通1. 概述 本文主要记录下基础的硬件知识&#xff0c;方便自己查阅。 2. 判断MOS管的导通 在产品硬件设计中&#xff0c;有时需要程序控制一些电源使能。 1.原理图已经标出了G极(gate)—栅极、S极(source)—源极、D极(drain)—漏极。 如果没有标…

「基础篇」机器学习概览

文章目录1. 什么是机器学习2. 引入机器学习3. 应用场景4. 机器学习分类4.1. 有无人类监督4.2. 是否增量学习4.3. 泛化方式5. 主要挑战6. 测试与验证1. 什么是机器学习 机器学习&#xff08;Machine Learning&#xff0c;ML&#xff09;是一个研究领域&#xff0c;让计算机无需…