【C语言】qsort的秘密

news2024/11/23 11:01:49

一,本文目标 

qsort函数可以对任意类型数据甚至是结构体内部的数据按照你想要的规则排序,它的功能很强大,可是为什么呢?

我将通过模拟实现qsort函数来让你对这整个过程有一个清晰的深刻的理解。


二,qsort函数原型

void qsort (void* base,//要排序的对象的第一个元素的首地址
            size_t num, //对象的个数
            size_t size,//每一个对象的大小 Size in bytes
            int (*compar)(const void*,const void*));//Pointer to a function that compares two elements.(并且这个函数要自己写)

        qsort函数底层通过快速排序进行,但是这并不是我们感兴趣的点,我想要知道qsort为什么可以对任意类型数据进行排序,与它用什么排序算法排序没有关系,所以,我们用相对简单的冒泡排序来代替快速排序的算法,把冒泡排序赋予可以对任意类型数据进行排序的强大功能。


三,冒泡排序——大数沉底,小数上浮

 对于冒泡排序,其基本思想是大数沉底,小数上浮;

这里直接给出源码:

void Bubble_sort(int arr[], int size)
{
	int j,i;
	for (i = 0; i < size-1;i++)//排序趟数等于元素个数-1
	{
		int f = 0;
		for (j = 0; j < size - 1 - i; j++)//每一趟都有一个元素复位,需要排序的次数每次-1
		{
			if ( arr[j] > arr[j+1] )
			{
                int tem = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tem;
				f = 1;
			}
		}
		if (f == 0)	
		{
            break;
        }
	}

3.1相对于qsort,冒泡排序的局限性

        只能排序整形数据 

四,两个问题:

4.1不定类型比较问题

        1.对于if内部的判断,虽然字符型可以用 >,<,= 比较,但是对于字符串类型,无法通过常规方法比较;

4.2不定类型交换问题

        2.对于交换部分,由于不同元素的类型不同,占用的内存空间也不同,如何判断传入数据的类型,并实现两个数据的交换呢?


五,改造冒泡排序

我们将自己模拟实现的qsort函数称my_qsort,实现如下:


void my_qsort(void* base,size_t sz,size_t width,int (*cmp)(const void* p1,const void* p2))//cmp 是函数指针,在my_sort中被多次调用
{//它指向的函数是int_cmp,int_cmp就是回调函数
	int i = 0;
	for(i = 0;i < sz - 1;i++)//趟数不变
	{
		int j = 0;
		for(j = 0;j < sz - i - 1;j++)//每一趟进行的比较数不变
		{
			if(cmp((char*)base + j * width,(char*)base + (j + 1) * width) > 0 )//判断要改变
			{
				Swap((char*)base + j * width,(char*)base + (j + 1) * width,width);
			}
		}
	}
}

5.1不同类型比较

我们定义一个cmp函数来实现比较:

5.1.1cmp实参

我们传入排序的数组的首元素地址base,将base强制类型转化为 char* 类型,这样base与整数的运算就只会跨过这个整数个字节;

如果再知道每个元素的大小(长度),那么我们就可以精确的访问到每个元素了;


怎么理解呢? 

 e.g.1:对于int

 图中的base指针的位置是 j == 1的位置

 e.g.2:对于char

 

 图中的base指针的位置是 j == 5 的位置 

5.1.2cmp形参

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

由于比较的对象是整型,所以定义一个比较整形的函数,void* 类型不能直接解引用,所以将p1,p2强制类型转化,将两数相减返回。

5.2不定类型交换问题

定义交换函数Swap 

void Swap(char* buf1,char* buf2,int width)//按字节逐个交换
{
	for(int i = 0;i < width;i++)
	{
		char tem = *buf1;
		*buf1 = *buf2;
		*buf2 = tem;
		buf1++;//如果是整型,则要遍历四个字节
		buf2++;
	}
}

这时候,我们就知道定义width的意义了,对于一个数据类型,我们虽然不知道它的类型,但是我们可以根据它的长度,一个一个字节地交换数据。


整体代码运行

对于所有类型,我们的冒泡排序都可以排序了,它的作用与库函数qsort一致,仍然需要我们自己写cmp函数:;

对int型数据的排序:

#include<stdio.h>
#include<string.h>
int int_cmp(const void* p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1,char* buf2,int width)//按字节逐个交换
{
	for(int i = 0;i < width;i++)
	{
		char tem = *buf1;
		*buf1 = *buf2;
		*buf2 = tem;
		buf1++;//如果是整型,则要遍历四个字节
		buf2++;
	}
}

void my_qsort(void* base,size_t sz,size_t width,int (*cmp)(const void* p1,const void* p2))//cmp 是函数指针,在my_sort中被多次调用
{//它指向的函数是int_cmp,int_cmp就是回调函数
	int i = 0;
	for(i = 0;i < sz - 1;i++)//趟数不变
	{
		int j = 0;
		for(j = 0;j < sz - i - 1;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 test1(void)
{
	int arr[] = {2,5,8,9,6,3,1,4,7,0};
	int sz = sizeof(arr)/sizeof(arr[0]);
	my_qsort(arr,sz,sizeof(arr[0]),int_cmp);
	
	for(int i = 0;i < sz;i++)
	{
		printf("%d ",arr[i]);
	}
}

int main()
{
    test1();
    return 0;
}

对于结构体类型,也可根据结构体内部某一元素的特征排序:

结构体类型

对结构体内的int型数据排序:


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

int struct_cmp_by_age(const void* p1,const void* p2)
{
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}

void test2(void)
{
	struct stu arr[] = {{"zhangsan",18},{"lisi",25},{"wangwu",30},{"xiaoming",40}};
	int sz = sizeof(arr)/sizeof(arr[0]);
	my_qsort(arr,sz,sizeof(arr[0]),struct_cmp_by_age);
	for(int i = 0;i < sz;i++)
	{
		printf("%s %d\n",arr[i].name,arr[i].age);
	}
}

int main()
{
    test2();
    return 0;
}

对结构体内的char型数据排序:


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

int struct_cmp_by_name(const void* p1,const void* p2)
{
	return strcmp(((struct stu*)p1)->name,((struct stu*)p2)->name);
}

void test3(void)
{
	struct stu arr[] = {{"zhangsan",18},{"lisi",25},{"wangwu",30},{"xiaoming",40}};
	int sz = sizeof(arr)/sizeof(arr[0]);
	my_qsort(arr,sz,sizeof(arr[0]),struct_cmp_by_name);
	for(int i = 0;i < sz;i++)
	{
		printf("%s %d\n",arr[i].name,arr[i].age);
	}
}


int main()
{
	
	//test1();
	//test2();
	test3();
	return 0;
}


1.对字符串的比较用到<string.h>中的strcmp函数,而它的返回值正好符合qsort函数第四个参数:函数的要求 

 

 

第一个字符串大于第二个,strcmp返回大于0的数,对应qsort函数要求第四个函数的返回值大于0,表示第一个参数大一第二个。 


本文回顾 

目录

一,本文目标 

二,qsort函数原型

三,冒泡排序——大数沉底,小数上浮

3.1相对于qsort,冒泡排序的局限性

四,两个问题:

4.1不定类型比较问题

4.2不定类型交换问题

五,改造冒泡排序

5.1不同类型比较

5.1.1cmp实参

5.1.2cmp形参

5.2不定类型交换问题

整体代码运行

对int型数据的排序:

结构体类型

对结构体内的int型数据排序:

对结构体内的char型数据排序:


完~

未经作者同意禁止转载

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

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

相关文章

JDBC编程方法及细节

JDBC&#xff08;Java Database Connectivity&#xff09;是Java编程语言用于连接和操作数据库的API&#xff08;Application Programming Interface&#xff09;。它为开发人员提供了一组Java类和接口&#xff0c;用于与各种关系型数据库进行通信。使用JDBC&#xff0c;开发人…

【Linux】fork()

文章目录 一、fork()是什么&#xff1f;二、fork()干了什么&#xff1f;三、fork()怎么用&#xff1f; 一、fork()是什么&#xff1f; fork()函数其实是在Linux系统中用于创建一个新的进程。让我们看看Linux中是怎么描述的&#xff1f;运行man fork。 RETURN VALUE On success…

探究Kafka原理-3.生产者消费者API原理解析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44…

github国内访问小解(windows)

git 下载安装 使用 github 前必须确保电脑上已经安装了 Git&#xff0c;可以从 Git 官方网站去下载。 官方的网站在国内访问会比较慢&#xff0c;这里可以选择国内镜像&#xff1a;https://registry.npmmirror.com/binary.html?pathgit-for-windows/ github 之旅 确认电脑已…

使用向日葵开机棒进行远程开机

文章目录 1\. 前言2\. 说明3\. 开机棒设置4\. 电脑端设置4.1. 电脑端允许网卡唤醒4.1.1. 关闭设备节能4.2. 将电脑端设备加入设备列表 5\. 手机端5.1. 添加开机棒5.2. 绑定主机5.2.1. 添加成功的主机 6\. 唤醒 1. 前言 如果我们出差在外或者人不在实验室&#xff0c;如果可以使…

Spring 七大组件

文章目录 Spring 七大组件 Spring 七大组件 核心容器(Spring core) 核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean&#xff0c;它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式…

Linux python安装 虚拟环境 virtualenv,以及 git clone的 文件数据, 以及 下资源配置

根目录创建 venvs 文件夹 sudo mkdir /venvs 进入 /venvs 目录 cd /venvsp 创建虚拟环境&#xff0c;前提要按照 python3 安装 的 命令 sudo apt install python3 sudo python3 -m venv 虚拟环境名 激活虚拟环境 source /venvs/zen-venv/bin/activate 安装flask pip install fl…

测试工具JMeter的使用

目录 JMeter的安装配置 测试的性能指标 TPS 响应时长 并发连接 和 并发用户 CPU/内存/磁盘/网络 负载 性能测试实战流程 JMeter JMeter快速上手 GUI模式 运行 HTTP请求默认值 录制网站流量 模拟间隔时间 Cookie管理器 消息数据关联 变量 后置处理器 CSV 数据文…

Spring AOP:什么是AOP? 为什么要用AOP?如何学习AOP?

文章目录 &#x1f386;前言1.为什么要用 AOP3.如何学习去 AOP?3.1 AOP 的组成切面&#xff08;Aspect&#xff09;连接点&#xff08;Join Point&#xff09;切点&#xff08;Pointcut&#xff09;通知&#xff08;Advice&#xff09; 3. Spring AOP 实现3.1 普通的方式实现 …

切换服务器上自己用户目录下的 conda 环境和一个外部的 Conda 环境

如果我们有自己的 Miniconda 安装和一个外部的 Conda 环境&#xff08;比如一个全局安装的 Anaconda&#xff09;&#xff0c;我们可以通过修改 shell 环境来切换使用它们。这通常涉及到更改 PATH 环境变量&#xff0c;以便指向你想要使用的 Conda 安装的可执行文件&#xff1a…

二维数值型数组例题2

1、内部和 题目描述 给定一个m行n列的二维矩阵&#xff0c;求其内部元素和 输入要求 第一行为两个整数&#xff1a;m和n&#xff08;0<m,n<10&#xff09;&#xff0c;接下来输入m*n的二维矩阵 输出要求 二维矩阵内部元素和 输入样例 3 3 1 2 3 4 5 6 7 8 9 …

2023年11月18日~11月24日周报(调试OpenFWI代码)

目录 一、前言 二、完成情况 2.1 OpenFWI工程文件框架的理解 2.2 InversionNet网络的理解 2.3 dataset.py的理解 三、遇到的部分问题及解决 3.1 数据读取 3.2 Dataloader中设置num_workers-MemoryError 3.3 RuntimeError: CUDA out of memory 3.4 loss值的变化 四、相…

MySQL介绍及安装

MySQL介绍及安装 一、MySQL概述 1、关系型数据库与非关系型数据库 RDBMS&#xff08;relational database management system&#xff09;&#xff0c;既关系型数据库管理系统。 简单来说&#xff0c;关系型数据库&#xff0c;是指采用了二维表格来组织数据的数据库。 扩展…

Codeforces Round 786 (Div. 3) D. A-B-C Sort

D. A-B-C Sort 步骤 1 &#xff1a;当 a不为空时&#xff0c;从 a中取出最后一个元素&#xff0c;并将其移动到数组 b的中间。如果 b 当前长度为奇数&#xff0c;则可以选择&#xff1a;将 a 中的元素放到 b 中间元素的左边或右边。结果&#xff0c; a 变空&#xff0c; b 由 n…

SAS9.2软件“OLE:对象的类没有在注册数据库中注册“问题的解决. 2023-11-24

操作系统平台: win7 sp1 32bit (6.1.7601.26321 (Win7 RTM)) 1.安装依赖库(必须) Microsoft Visual C 2005 Redistributable Microsoft Visual C 2008 Redistributable 官方下载地址: 最新受支持的 Visual C 可再发行程序包下载 | Microsoft Learn 2.以管理员权限启动cmd.e…

Python常见报错以及解决方案梳理,快滚进收藏夹里吃灰吧!

前言 使用python难免会出现各种各样的报错&#xff0c;以下是Python常见的报错以及解决方法&#xff08;持续更新&#xff09;&#xff0c;快进入收藏吃灰吧&#xff01; AttribteError: ‘module object has no attribute xxx 描述:模块没有相关属性。可能出现的原因: 1.命…

[黑马程序员SpringBoot2]——原理篇1

目录&#xff1a; bean的加载方式(—)bean的加载方式(二)bean的加载方式(三)FactoryBeanproxyBeanMethod属性bean的加载方式(四)bean的加载方式(五)bean的加载方式(六)bean的加载方式(七)bean的加载方式(八)bean加载控制&#xff08;编程式)bean加载控制&#xff08;注解式)be…

加速 Selenium 测试执行最佳实践

Selenium测试自动化的主要目的是加快测试过程。在大多数情况下&#xff0c;使用 Selenium 的自动化测试比手动测试执行得特别好。在实际自动化测试实践中&#xff0c;我们有很多方式可以加速Selenium用例的执行。 我们可以选择使用不同类型的等待、不同类型的 Web 定位器、不同…

libmosquitto库的一个bug,任务消息id(mid)分配后不起作用

代码如图所示: 当订阅了所有主题后,每个主题的mid是他们的下标索引加100的数字,可是实际打印出来的值是: mid依然是1,2,这个参数在这里失效了,不知道是bug还是mqtt的什么机制?

2023 最新 PDF.js 在 Vue3 中的使用

因为自己写业务要定制各种 pdf 预览情况&#xff08;可能&#xff09;&#xff0c;所以采用了 pdf.js 而不是各种第三方封装库&#xff0c;主要还是为了更好的自由度。 一、PDF.js 介绍 官方地址 中文文档 PDF.js 是一个使用 HTML5 构建的便携式文档格式查看器。 pdf.js 是社区…