【C语言】用冒泡排序实现my_qsort

news2025/1/16 8:13:34

大家好,我是苏貝,本篇博客带大家了解如何用冒泡排序实现my_qsort,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • 一. 前言
  • 二. 冒泡排序
  • 三. 4个参数
    • 3.1 第一个参数void* base
    • 3.2 第二个参数szie_t num
    • 3.3 第三个参数szie_t size
    • 3.4 第四个参数int ( * cmp)(const void* e1,const void* e2)
  • 四. bubble_sort函数
  • 五. 排序
    • 5.1 对整型数组排序(char/short/int/long)
  • 5.2 对浮点型数组排序(float/double)
    • 5.3 对字符串长度排序
    • 5.4 对字符串大小排序
    • 5.5 对结构体排序

一. 前言

用冒泡排序实现my_qsort?你或许觉得没有必要这样做,有现成的qsort函数,为什么还要自己写一个呢?于我而言,它可以让我对冒泡排序和qsort函数的印象加深。至于这到底有没有用,仁者见仁,智者见智吧。好了,就说这么多,现在让我们来回忆一下什么是冒泡排序和qsort函数。了解qsort函数


二. 冒泡排序

冒泡排序的原理:从左往右,依次比较相邻元素的大小,若符合条件,则两元素位置交换,每一趟可以找出最大值或最小值,并显示在序列的最右边。注意:冒泡排序只能排序整形序列,而qsort函数可以排序任意类型的序列

以想要升序排序为例,数组{5,2,4,3},条件:如果相邻元素的左边>右边,则两元素位置交换。第一趟:5>2,交换位置。5>4,交换位置。5>3,交换位置。所以第一趟结束后数组为{2,4,3,5}。第二趟:2<4,不符合条件,位置不变。4>3,交换位置。所以第二趟结束后数组为{2,3,4,5}。第三趟,2<3,位置不变

代码:

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 tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

结果为:
在这里插入图片描述


三. 4个参数

3.1 第一个参数void* base

回顾上面的bubble_sort函数,它的第一个参数是int arr[ ],也可以写成int* arr,意思是接受一个整形数组的首元素地址。可是我们想要的是能接受任意类型的指针,所以用int* 就不合适,采用void* (void* 能接受任意类型的指针) ,所以第一个参数写为void* base

3.2 第二个参数szie_t num

上面的bubble_sort函数的第二个参数是int sz,代表数组有多少个元素,其实用int sz也可以,只是如果我们想更贴近qsort函数的话,将int改成size_t(无符号整型)也没有问题,写成szie_t num

3.3 第三个参数szie_t size

上面的bubble_sort函数并没有第三个参数,但是qsort函数有,意思是数组中的一个元素占多少个字节。因为我们不知道要排序的数组是什么类型的,所以如果没有第三个参数,我们无法表示出除首元素以外的地址,也就无法交换相邻元素

3.4 第四个参数int ( * cmp)(const void* e1,const void* e2)

第四个参数是用来判断相邻元素的关系,不能直接使用>,<,=的原因是如果我们想排序多个字符串,那么我们是不能使用>,<,=的,我们需要使用strcmp函数。不同类型的数组排序的函数可能不一样,像qsort函数一样,程序员又不知道用户想要排序的是什么类型的数组,所以程序员只能用函数指针cmp来接收用户自己写的cmp函数,而用户当然知道自己想要排序的数组的类型,所以用户能写出判断相邻元素的关系的函数

cmp函数的细节:
1.e1和e2是相邻元素的地址,被const修饰,保护e1和e2不能被修改
2.因为不知道排序的数组是什么类型的,所以e1和e2都是void * 类型的
3.如果相邻元素的左边>右边,返回>0的数;左边==右边,返回0;左边<右边,返回<0的数。所以返回值的类型为int

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1,const void* e2))
{}

四. bubble_sort函数

虽然函数的参数变化了很多,但函数原理并没有改变,依旧是比较相邻元素的大小,若符合条件,则两元素位置交换,每一趟可以找出最大值或最小值,并显示在序列的最右边。所以下面的框架不变(只将之前的sz变成了num),变得只是第二个for循环里面的内容,而里面的内容就是判断相邻元素的左边是否大于右边(以想要升序排序为例),如果是则交换位置,否则位置不变

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1,const void* e2))
{
	int i = 0;
	//排几趟
	for (i = 0; i < num - 1; i++)
	{
		int j = 0;
		//一趟排几次
		for (j = 0; j < num - 1 - i; j++)
		{
		}
	}
}

好的,那我们现在就想办法如何填写里面的代码,先是判断相邻元素的大小,用cmp函数,函数的两个参数分别为相邻元素的地址。那它们的地址该如何表示呢?首元素地址就是base,不用多说,那第二个元素呢?base+1?不行,我们不知道base的具体类型,所以base+1不知道会跳过几个字节。
那不妨将base强制类型转化为char* ,这样第二个元素(下标为1)的地址为(char*)base+1* size,第三个元素(下标为2)的地址为(char*)base+2* size,所以下标为j的地址为(char*)base+ j * size,代码为:

if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{}

好的,那我们继续完善if语句后面的代码,代码的目的是将两元素交换位置,编写swap函数实现交换功能,swap函数的第一、二个参数自然是相邻元素的地址,即(char*)base + j * size, (char*)base + (j + 1) * size,它们都是char * 类型的,所以用两个char * 类型的指针来接收。
但问题又来了,我们平时实现a与b交换,是通过一个变量c来进行的,如下:

	int a = 10;
	int b = 20;
	int c = a;
	a = b;
	b = c;

上面能交换的原因是我们知道了a和b的类型都是int,所以再创造一个int类型的变量作它们的中转站,即可实现交换。但我们事先并不知道数组的类型,所以不能创建一个和数组同类型的变量实现交换。那不如将两元素的每一个字节都交换(如下图),刚好它们是用char * 指针buf1、buf2来接收的,buf1++,buf2++即可访问两元素的每一个字节,因此我们也需要数组元素的字节数size作为swap函数的第三个参数
在这里插入图片描述
所以if语句后面的代码为:

swap((char*)base + j * size, (char*)base + (j + 1) * size, size);

swap函数:

void swap(char* buf1, char* buf2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

所以bubble_sort函数的全部代码为:

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1,const void* e2))
{
	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 * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

五. 排序

对任意类型的数组排序都需要下面的代码,故把这些代码单独放在最前面,其余的main函数和cmp函数都因数组类型的变化而有所改变,所以将main函数和cmp函数单独写出来

void swap(char* buf1, char* buf2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1,const void* e2))
{
	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 * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

5.1 对整型数组排序(char/short/int/long)

字符在内存中存储的是字符的ASCII码值,ASCII码是整型,所以char的写法同int

void cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//升序
	//return *(int*)e2 - *(int*)e1;//降序
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//升序
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	//打印数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

5.2 对浮点型数组排序(float/double)

void cmp_double(const void* e1, const void* e2)
{
	return *(double*)e1 > *(double*)e2 ? 1 : -1;//升序
	//return *(double*)e1 < *(double*)e2 ? 1 : -1;
}

int main()
{
	double arr[] = { 3.2 ,4.5, 2.4, 1.3 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//升序
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_double);
	//打印数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%lf ", arr[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

5.3 对字符串长度排序

void cmp_string(const void* e1, const void* e2)
{
	return strlen((char*)e1) - strlen((char*)e2);//升序
	//return strlen((char*)e2) - strlen((char*)e1);//降序
}

int main()
{
	char arr[][20] = { "helloworld","yes,sir","dian ge zan ba" };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr[0], sz, sizeof(arr[0]), cmp_string);
	int i = 0;
	for (i = 0; i < sz; i++)
		printf("%s\n", arr[i]);
	return 0;
}

在这里插入图片描述

5.4 对字符串大小排序

void cmp_string(const void* e1, const void* e2)
{
	return strcmp((char*)e1, (char*)e2);//升序
	//return strcmp((char*)e2, (char*)e1);//降序
}

int main()
{
	char arr[][20] = { "helloworld","yes,sir","dian ge zan ba" };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//升序
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_string);
	//打印数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s\n", arr[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

5.5 对结构体排序

struct Stu
{
	char name[20];
	int age;
};

void cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//升序
	//return ((struct Stu*)e2)->age - ((struct Stu*)e1)->age;//降序
}

int main()
{
	struct Stu arr[] = { {"zhang",20},{"lisi",10},{"wangwu",30} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//升序
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	//打印
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s\t", arr[i].name);
		printf("%d\n", arr[i].age);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

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

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

相关文章

驱动开发,stm32mp157a开发板的led灯控制实验(再优化),使用ioctl函数,通过字符设备驱动分步注册方式编写LED驱动,完成设备文件和设备的绑定

1.实验目的 编写LED灯的驱动&#xff0c;在应用程序中编写控制LED灯亮灭的代码逻辑实现LED灯功能的控制&#xff1b; 2. LED灯相关寄存器分析 LED1->PE10 LED1亮灭&#xff1a; RCC寄存器[4]->1 0X50000A28 GPIOE_MODER[21:20]->01 (输出) 0X50006000 GPIOE_ODR[10]-&…

CTyunOS安装MySQL8.0

CTyunOS安装MySQL8.0 1.yum安装2. 启动配置3.注意事项 最近天翼云搞活动&#xff0c;购了一台4U8G的服务器&#xff0c;这里讲解一下如何安装Mysql吧。 1.yum安装 yum install -y mysql yum install -y mysql-common yum install -y mysql-errmsg yum install -y mysql-libs y…

Jenkins :添加node权限获取凭据、执行命令

拥有Jenkins agent权限的账号可以对node节点进行操作&#xff0c;通过添加不同的node可以让流水线项目在不同的节点上运行&#xff0c;安装Jenkins的主机默认作为master节点。 1.Jenkins 添加node获取明文凭据 通过添加node节点&#xff0c;本地监听ssh认证&#xff0c;选则凭…

Ansible 自动化运维工具部署主从数据库+读写分离

文章目录 Ansible 自动化运维工具部署主从数据库读写分离一、主从复制和读写分离介绍二、准备工作&#xff08;1&#xff09;节点规划&#xff08;2&#xff09;修改主机名&#xff08;3&#xff09;免密&#xff08;4&#xff09;配置IP映射&#xff08;5&#xff09;安装ansi…

电荷型 和 IEPE/ICP型振动传感器的比较

PE(压电式)和IEPE(集成电路压电式,PCB公司叫做ICP)传感器的对比说明,供各位参考。 1. PE/IEPE传感器的敏感元件均为压电晶体,通过压电效应感受被测物理量。 2.PE传感器:输出电荷量,也叫电荷传感器。不需要供电,两根信号线,可直接接入电荷放大器进行测量。 优点―――结…

Java————List

一 、顺序表和链表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c; 常见的线性表&#xff1a;顺序表、链表、栈、队列… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直…

基础篇之SDK编译

文章目录 一、 Ubuntu系统固件下载1. 固件下载2 放入SDK根目录中 二、编译SDK三、说明 一、 Ubuntu系统固件下载 1. 固件下载 在资源下载页面下载Ubuntu Rootfs固件&#xff0c;文件夹有三个文件&#xff0c;其区别如下&#xff0c;根据情况进行选择下载 资源名称作用Ubuntu2…

【毕设选题】opencv 图像识别 指纹识别 - python

文章目录 0 前言1 课题背景2 效果展示3 具体实现3.1 图像对比过滤3.2 图像二值化3.3 图像侵蚀细化3.4 图像增强3.5 特征点检测 4 OpenCV5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往…

网络安全深入学习第三课——热门框架漏洞(RCE—Struts2远程代码执行)

文章目录 一、Struts2框架介绍二、Struts2远程代码执行漏洞三、Struts2执行代码的原理四、Struts2框架特征五、漏洞手工POC六、漏洞工具复现 一、Struts2框架介绍 ------ Struts2是apache项目下的一个web 框架&#xff0c;普遍应用于阿里巴巴、京东等互联网、政府、企业门户网…

Linux——Shell脚本编程(2)

一、Shell变量 Linux Shell 中的变量分为&#xff0c;系统变量 和 用户自定义变量 (这个用的比较多)。 系统变量 : $HOME、$PWD、$SHELL、$USER 等等&#xff0c;比如 : echo $HOME 等等.. 显示当前shell中所有变量 : set 举例说明&#xff1a; 二、设置环境变量 记得在注释…

ReactNative 网络库

What JS判断网络状态不准确 react-native-netinfo在Android中的结构 type-CellularGeneration&#xff1a;G网模式枚举 type-ConnectionType&#xff1a;网络连接类型 AmazonFireDeviceConnectivityPoller&#xff1a;Amazon设备网络适配&#xff0c;可忽略 BroadcastRec…

DP专题1 斐波那契数列II

题目&#xff1a; 思路&#xff1a; 通过样例 3 &#xff0c;我们可以看出&#xff0c;将我们所对应的每个步骤进行拆分后&#xff0c;可以知道&#xff0c;每个步骤中都是调用了前面我们计算过的重复计算&#xff0c;所以这里的 dp[i] 中&#xff0c;i 表示 相应的Fbn(i) 的结…

数据结构入门-14-排序

一、归并排序MergeSort 更加复杂的递归算法 O(nlogn)的时间复杂度 1.1 归并思想 将一个数组一分为二 &#xff0c;分别排序&#xff0c;得到两个排序后的子数组 对两个子数组排序的方法还是继续划分 MergeSort(arr, l, r) 对 arr数组的 l 到 r 区间进行排序1.2 归并步骤 递…

Nodejs 第十六章(ffmpeg)

FFmpeg 是一个开源的跨平台多媒体处理工具&#xff0c;可以用于处理音频、视频和多媒体流。它提供了一组强大的命令行工具和库&#xff0c;可以进行视频转码、视频剪辑、音频提取、音视频合并、流媒体传输等操作。 FFmpeg 的主要功能和特性&#xff1a; 格式转换&#xff1a;…

一文详解TCP三次握手四次挥手

文章目录 TCP的三次握手和四次挥手三次握手四次挥手 TCP的三次握手和四次挥手 基本概念 SYN&#xff08;Synchronize Sequence Numbers&#xff0c;同步序列数字&#xff09;&#xff1a;用于建立连接的同步信号。 SYN 序列号的作用是用于标识每个数据包中的字节流的起始位置。…

Intellij idea 2023 年下载、安装教程、亲测可用

文章目录 1 下载与安装IDEA2 常用设置设置 Java JDK 版本自动导入包、移除包IDEA 自动生成 author 注释签名java.io.File 类无法自动提示导入&#xff1f;高亮显示与选中字符串相同的内容IDEA 配置 MavenIDEA 连接 Mysql 数据库 3 参考文章 1 下载与安装IDEA 首先先到官网下载…

python学习之【模块】

前言 上一篇文章 python学习之【深拷贝】中学习了python中的深浅拷贝学习内容&#xff0c;这篇文章接着学习python中的模块。 什么是模块 在python中&#xff0c;一个文件&#xff08;以“.py”为后缀名的文件&#xff09;就叫做一个模块&#xff0c;每一个模块在python里都…

爬虫框架Scrapy学习笔记-2

前言 Scrapy是一个功能强大的Python爬虫框架&#xff0c;它被广泛用于抓取和处理互联网上的数据。本文将介绍Scrapy框架的架构概览、工作流程、安装步骤以及一个示例爬虫的详细说明&#xff0c;旨在帮助初学者了解如何使用Scrapy来构建和运行自己的网络爬虫。 Scrapy架构概览…

Safetynet论文精读

基本信息 团队&#xff1a;Level 5&#xff0c; Toyota收购的Lyft自动驾驶团队&#xff08;对&#xff0c;这个团队名字就叫Level 5&#xff09; 年份&#xff1a;2021 官网&#xff1a;https://www.self-driving-cars.org/papers/2022-safetynet&#xff08;对&#xff0c;他…

Godot 和 VScode配置C#环境注意事项

前言 尽管有些博主会建议如果我们熟悉C#的话&#xff0c;最好还是使用GDscript&#xff0c;而且对于小白上手也相对简单&#xff0c;但是C#的性能终究还是比动态语言好&#xff0c;也相比CPP简单些&#xff0c;尽管现在Godot还是有些问题&#xff0c;比如不像unity那样适配swit…