C语言之qsort()函数的模拟实现

news2024/11/15 4:34:22

C语言之qsort()函数的模拟实现

文章目录

  • C语言之qsort()函数的模拟实现
    • 1. 简介
    • 2. 冒泡排序
    • 3. 对冒泡排序进行改造
    • 4. 改造部分
      • 4.1 保留部分的冒泡排序
      • 4.2 比较部分
      • 4.3 交换部分
    • 5. bubble_sort2完整代码
    • 6. 使用bubble_sort2来排序整型数组
    • 7. 使用bubble_sort2来排序结构体数组
      • 7.1 按名字来排序结构体数组
      • 7.2 按年龄来排序结构体数组

1. 简介

qsort()函数全称为Quicksort,因为底层使用的是快速排序,对初学者来说只学过冒泡排序,使用我们使用冒泡排序来实现下qsort()函数,qsort()函数(冒泡排序版)

不知道qsort函数怎么使用的可以看看这篇qsort函数

2. 冒泡排序

既然要使用冒泡排序改模拟实现qsort,那得知道什么是冒泡排序
代码如下:

#include <stdio.h>

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[] = { 1,4,7,2,5,8,3,10,6,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;
}

3. 对冒泡排序进行改造

void bubble_sort(int arr[], int sz);
void qsort (void* base, 
            size_t num, 
            size_t size,            
            int (*compar)(const void*,const void*));

先来看看冒泡排序和qsort函数的函数声明
要想用冒泡排序模拟实现qsort函数,首先函数的形参要一致

void bubble_sort2(void* base,
                  size_t sz,
                  size_t width,  
                  int (*cmp)(const void* p1,const void* p2));

仿照着qsort的形参来写

  1. void*:由于我们不知道要排序什么数组,所以我们使用void*来接收
  2. sz:为数组中元素的个数
  3. width:为数组中一个元素的大小(单位为字节)
  4. int (cmp)(const void p1,const void* p2):是函数指针
    这个函数指针指向的函数是用来比较两个元素的大小
    p1指向一个元素,p2也指向一个元素

函数的声明部分写好了,就得改造函数内部了
先来看看冒泡排序中的代码
在这里插入图片描述

  1. 首先函数的形参得更改吧 改为上边的函数声明
  2. 趟数取决于数组中元素个数使用不需要修改
  3. 冒泡排序只能排序整型,所以一趟冒泡排序中的比较部分需要修改
  4. 交换两个元素的位置也需要修改

4. 改造部分

4.1 保留部分的冒泡排序

先将冒泡排序还能使用的部分保留下来

void bubble_sort2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		//一趟冒泡排序
		for (j = 0; j < sz - 1 - i; j++)
		{
			//比较两个元素的大小
			if ()
			{
				//Swap用于交换两个元素的位置
				Swap();
			}
		}
	}
}

int main()
{
	int arr[] = { 1,4,7,2,5,8,3,10,6,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;
}

其中的比较部分和交换部分需要我们自己来实现

4.2 比较部分

cmp((char*)base + j * width, (char*)base + (j + 1) * width)
  1. 由于我们得到的数组名,也就是首元素的地址,不知道第二个元素的地址
    我们可以通过指针偏移来找到第二个元素

  2. 我们得到的是void类型的数据,我们需要将其强制转换成char类型的数据,以便于我们进行指针偏移

  3. 不知道数组的类型,我们只知道一个元素的大小,我们可以通过(char*)base + j + width的方式来找到一个元素的地址,(char*)base + (j+1) + width的方式来找到后一个元素的地址
    用来模拟arr[j] 和 arr[j+1]
    在这里插入图片描述

  4. 为什么其他类型的指针不行呢,我来举个例子:

假设用来排序double类型的数据,也就是每个元素是8字节 width = 8
(char*)base + j * width 和 (char*)base + (j+1) * width
当j等于0时,char类型的指针会偏移8个字节,找到第二个元素
如果换成其他类型的指针 int* 在这个情况下就不能使用了,只有char*类型的指针偏移是1个字节,适用于任意类型的排序

4.3 交换部分

void Swap(char* p1, char* p2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
  1. 由于在比较部分已经将数据强制转换成char类型,所以Swap函数的形参就可以使用char来接收
  2. 由于不知道接收的是什么类型的数据,但是我们知道每个元素的大小,我们可以通过一个字节一个字节进行交换,和冒泡排序一样定义一个临时变量来交换两个元素,交换完一个字节的内容之后,p1++ p2++来找到第二个字节的内容,直到交换完成

5. bubble_sort2完整代码

void Swap(char* p1, char* p2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
void bubble_sort2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		//一趟冒泡排序
		for (j = 0; j < sz - 1 - i; j++)
		{
			//比较两个元素的大小
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//Swap用于交换两个元素的位置
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}

6. 使用bubble_sort2来排序整型数组

和使用qsort函数一样,第四个形象需要根据需要排序的数据类型来编写函数,然后将其传给qsort函数,整型数据的比较只需要两个元素作差就好了
代码如下:

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

排序整型数组的完整代码:

#include <stdio.h>

void Swap(char* p1, char* p2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
void bubble_sort2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		//一趟冒泡排序
		for (j = 0; j < sz - 1 - i; j++)
		{
			//比较两个元素的大小
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//Swap用于交换两个元素的位置
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}

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

int main()
{
	int arr[] = { 1,4,7,2,5,8,3,10,6,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;
}

代码运行结果如下:
在这里插入图片描述

7. 使用bubble_sort2来排序结构体数组

由于结构体中的数据类型较多,可以选择一种来排序,然后根据不同的数据类型来排序
按以下两种数据来举例子

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

```c
int main()
{
	struct Stu arr[] = { {"zhangsan",25},{"lisi",18} ,{"wangwu",30} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort2(arr, sz, sizeof(arr[0]), cmp_struct_by_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	return 0;
}

7.1 按名字来排序结构体数组

字符串的比较是比较ASCII,不是比较字符串的长度

int cmp_struct_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
	//return strcmp((*(struct Stu*)p1).name, (*(struct Stu*)p2).name);
	//两段代码等价
}

完整代码如下:

#include <stdio.h>
#include <string.h>

void Swap(char* p1, char* p2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
void bubble_sort2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		//一趟冒泡排序
		for (j = 0; j < sz - 1 - i; j++)
		{
			//比较两个元素的大小
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//Swap用于交换两个元素的位置
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}

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

int cmp_struct_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
	//return strcmp((*(struct Stu*)p1).name, (*(struct Stu*)p2).name);
	//两段代码等价
}

int main()
{
	struct Stu arr[] = { {"zhangsan",25},{"lisi",18} ,{"wangwu",30} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort2(arr, sz, sizeof(arr[0]), cmp_struct_by_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	return 0;
}

代码运行结果如下:
在这里插入图片描述

7.2 按年龄来排序结构体数组

int cmp_struct_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
	//return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
	//两段代码等价
}

完整代码如下:

#include <stdio.h>
void Swap(char* p1, char* p2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
void bubble_sort2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		//一趟冒泡排序
		for (j = 0; j < sz - 1 - i; j++)
		{
			//比较两个元素的大小
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//Swap用于交换两个元素的位置
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}



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

int cmp_struct_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
	//return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
	//两段代码等价
}
int main()
{
	struct Stu arr[] = { {"zhangsan",25},{"lisi",18} ,{"wangwu",30} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort2(arr, sz, sizeof(arr[0]), cmp_struct_by_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	return 0;
}

代码运行结果如下:
在这里插入图片描述

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

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

相关文章

系列六、GC垃圾回收【四大垃圾算法-标记清除算法】

一、概述 标记清除算法分为两个阶段&#xff0c;即&#xff1a;标记和清除两个阶段&#xff0c;先标记出要回收的对象&#xff0c;然后统一回收这些对象。形如&#xff1a; 老年代一般是由标记清除或者标记清除 标记压缩的混合实现。 二、原理 用通俗的话解释一下标记清除算法…

LangChain 4用向量数据库Faiss存储,读取YouTube的视频文本搜索Indexes for information retrieve

接着前面的Langchain&#xff0c;继续实现读取YouTube的视频脚本来问答Indexes for information retrieve LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗…

一文了解Word2vec 阐述训练流程

一文了解Word2vec 阐述训练流程 个性嵌入&#xff08;Personality Embeddings&#xff09; 词嵌入&#xff08;Word Embeddings&#xff09; 嵌入向量效果分析 语言模型 模型介绍 模型训练 Word2vec训练方法 CBOW方法 Skip-gram方法 CBOW方法与Skip-gram方法总结 重构…

高精度算法【Java】(待更新中~)

高进度加法 在Java中可以使用BigInteger进行高精度计算&#xff0c;除此也可以仿照竖式相加的计算原理进行计算。 BigInteger 提供所有 Java 的基本整数操作符的对应物&#xff0c;并提供 java.lang.Math 的所有相关方法。另外&#xff0c;BigInteger 还提供以下运算&#xff1…

交易机器人-微信群通知

微信公众号:大数据高性能计算 1 背景 背景是基于人工去做交易本身无法做到24小时无时无刻的交易,主要是虚拟币本身它是24小时交易,人无法做到24小时盯盘,其次就是如果你希望通过配置更加复杂的规则甚至需要爬取最新的信息走模型进行量化交易的时候,就需要自己去做一些量化…

【Flink 问题集】The generic type parameters of ‘Collector‘ are missing

错误展示&#xff1a; Exception in thread "main" org.apache.flink.api.common.functions.InvalidTypesException: The return type of function main(CollectionDemo.java:33) could not be determined automatically, due to type erasure. You can give type in…

蓝桥杯单片机综合练习——工厂灯光控制

一、题目 二、代码 #include <reg52.h>sfr AUXR 0x8e; //定义辅助寄存器sbit S5 P3^2; //定义S5按键引脚 sbit S4 P3^3; //定义S4按键引脚unsigned char led_stat 0xff; //定义LED当前状态 unsigned char count 0; //定义50ms定时中断累…

我又开始贩卖焦虑了,机器视觉兄弟们,打工这生意盘不活了?让人逃离北上广深,是毒鸡汤吗?

我想大多数人和我想的一样&#xff0c;不要质疑自己的出身&#xff0c;也不必用一生去改变出身而获得融入感&#xff0c;思想富足这是我们留给自己一生最珍贵的礼物。也许一线城市容不下肉身&#xff0c;二三线城市容不下灵魂。那我回到生我养我的十八线小县城&#xff0c;这不…

【Linux】冯诺依曼体系结构、操作系统、进程概念、进程状态、环境变量、进程地址空间

目录 一、冯诺依曼体系结构二、操作系统(OS)1. 操作系统是什么2. 操作系统如何做管理3. 系统调用和库函数概念 三、进程1. 进程是什么&#xff1f;2. 描述进程-PCB3. 查看进程的方法 四、进程状态1 运行、阻塞和挂起状态2 Linux中的进程状态 五、进程优先级1. 什么是优先级2.查…

NSS [NCTF 2018]小绿草之最强大脑

NSS [NCTF 2018]小绿草之最强大脑 题目要求我们输入一个位数>21的正数&#xff0c;带入表达式并且计算结果。 查看源码发现hint&#xff0c;有源码泄露。 拿dirsearch扫一下。扫到了一个备份文件。 python dirsearch.py -u http://node4.anna.nssctf.cn:28805/访问/index.…

云原生专栏丨基于服务网格的企业级灰度发布技术

灰度发布&#xff08;又名金丝雀发布&#xff09;是指在黑与白之间&#xff0c;能够平滑过渡的一种发布方式。在其上可以进行A/B testing&#xff0c;即让一部分用户继续用产品特性A&#xff0c;一部分用户开始用产品特性B&#xff0c;如果用户对B没有什么反对意见&#xff0c;…

函数式编程框架 functionaljava 简介

文章目录 一、函数式编程起源二、functionaljava 框架简介 一、函数式编程起源 ​ 函数式编程起源于数理逻辑&#xff08;范畴论&#xff0c;Category Theory&#xff09;&#xff0c;起源于λ演算&#xff0c;这是一种演算法&#xff0c;它定义一些基础的数据结构&#xff0c…

人力资源小程序

人力资源管理对于企业的运营至关重要&#xff0c;而如今随着科技的发展&#xff0c;制作一个人力资源小程序已经变得非常简单和便捷。在本文中&#xff0c;我们将为您介绍如何通过乔拓云网制作一个人力资源小程序&#xff0c;只需五个简单的步骤。 第一步&#xff1a;注册登录乔…

【开源】基于Vue.js的智能教学资源库系统

项目编号&#xff1a; S 050 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S050&#xff0c;文末获取源码。} 项目编号&#xff1a;S050&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课…

黑马程序员 计算机网络(笔记)

参考文章&#xff1a;【黑马程序员】计算机网络概述~ 程序员必须掌握的入门基础知识&#xff01; 参考文章&#xff1a;黑马程序员Linux运维工作场景解决方案零基础到就业 参考文章&#xff1a;网络安全运维进阶教程&#xff0c;运维工程师深度学习教程 文章目录 linux入门到…

Windows核心编程 静态库与动态库

资源文件 .rc 文件 会被 rc.exe 变成 .res 文件(二进制文件) 在链接时链接进入 .exe 文件 一、如何保护源码 程序编译链接过程 不想让别人拿到源代码&#xff0c;但是想让其使用功能&#xff0c;根据上图观察&#xff0c;把自己生成的obj给对方&#xff0c;对方拿到obj后&…

nodejs+vue实验室上机管理系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计

用户&#xff1a;管理员、教师、学生 基础功能&#xff1a;管理课表、管理机房情况、预约机房预约&#xff1b;权限不同&#xff0c;预约类型不同&#xff0c;教师可选课堂预约和个人&#xff1b;课堂预约。 在实验室上机前&#xff0c;实验室管理员需要对教务处发来的上机课表…

Spring 配置

配置文件最主要的目的 : 解决硬编码的问题(代码写死) SpringBoot 的配置文件,有三种格式 1.properties 2.yaml 3.yml(是 yaml 的简写) SpringBoot 只支持三个文件 1.application.properties 2.application.yaml 3.application.yml yaml 和 yml 是一样的,学会一个就行…

SpringCloud微服务注册中心:Nacos介绍,微服务注册,Ribbon通信,Ribbon负载均衡,Nacos配置管理详细介绍

微服务注册中心 注册中心可以说是微服务架构中的”通讯录“&#xff0c;它记录了服务和服务地址的映射关系。在分布式架构中&#xff0c;服务会注册到这里&#xff0c;当服务需要调用其它服务时&#xff0c;就这里找到服务的地址&#xff0c;进行调用。 微服务注册中心 服务注…

PyTorch神经网络-激励函数

在PyTorch 神经网络当中&#xff0c;使用激励函数处理非线性的问题&#xff0c;普通的神经网络出来的数据一般是线性的关系&#xff0c;但是遇到比较复杂的数据的话&#xff0c;需要激励函数处理一些比较难以处理的问题&#xff0c;非线性结果就是其中的情况之一。 FAQ:为什么要…