C语言:qsort模拟实现

C语言:qsort模拟实现

    • 冒泡排序版
      • Swap - 交换函数
      • cmp - 比较函数
      • qsort - 排序主体


qsort函数是C语言中的一个标准函数,用于对数组进行快速排序。其函数原型如下:

void qsort(void *base, size_t nmemb, size_t size,int (*compar)(const void *, const void *));

参数解释:

  • base:指向数组首元素的指针。
  • nmemb:数组的元素个数。
  • size:每个元素的大小(以字节为单位)。
  • compar:比较函数的函数指针。

compar函数用于定义元素之间的比较规则。它需要返回一个整数值,表示两个元素之间的关系。具体的返回值规则如下:

  • 如果*ptr1 < *ptr2,则返回一个负整数。
  • 如果*ptr1 == *ptr2,则返回0。
  • 如果*ptr1 > *ptr2,则返回一个正整数。

使用示例:

int compare(const void *a, const void *b)
{return (*(int *)a - *(int *)b);
}int main(){int arr[] = {5, 2, 8, 10, 1};int n = sizeof(arr) / sizeof(arr[0]);qsort(arr, n, sizeof(int), compare);for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");return 0;
}

上述示例中的compare函数用于比较两个整数值。main函数中创建了一个整型数组,并调用qsort函数对其进行排序。最后,通过遍历数组打印出排序后的结果。

输出结果为:

1 2 5 8 10

qsort函数是C标准库中提供的用于快速排序的函数。通过传入一个比较函数,可以对任意类型数组进行排序。使用前需要引入stdlib.h头文件。

也许你现在有点迷惑,为什么这个qsort函数设计的这么麻烦,接下来我们来实现一个qsort函数,帮助大家理解。


冒泡排序版

鉴于大部分同学学习指针的时候,还没有学习过快速排序,所以我这里用冒泡排序进行代替。

先看到一个基本的冒泡排序:

void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void bubble(int* arr,int sz)
{for (int i = 0; i < sz; i++){for (int j = 0; j < sz - i - 1; j++){if (arr[j] > arr[j + 1]){swap(&arr[j], &arr[j + 1]);}}}
}

这个排序有以下缺陷:

  1. 只能排int类型的数据
  2. 只能排升序

qsort的实现就是围绕解决这两个问题。


Swap - 交换函数

首先,如果用户要传入不同类型的数据,每种数据占用的字节不同,请问如何实现数据的交换?

想要在函数内部实现函数外部的变量的数据交换,那就需要传址调用,这是毫无疑问的。但是我们的Swap函数要如何接收各种数据类型的指针?

答案毫无疑问就是使用void*类型的指针,使用void*类型的指针,可以接收任何类型的指针,唯一的缺点就是不能解引用void*类型的指针在解引用之前必须要强制类型转换,这下完了,我们不知道转化为什么类型的指针。

换一个思路,为什么我们一定要直接交换两个数据呢?现在我们可以利用void*类型的指针来接收待交换的两个变量的指针,内存中的数据是以字节存储的,我们可不可以一个一个字节进行交换,然后在外部传入这种变量类型的字节长度,从而知道要交换几个字节

思路理清后,我们看看Swap需要的参数:

void Swap(void* p1, void* p2, int size)
  • p1p2,待交换的两个变量的指针
  • size待交换的变量的类型所占字节数

接下来我们就要实现交换函数的内部了。

既然void*类型的指针需要强制转化后才能解引用,而我们要一个一个字节地交换数据。那是不是我们可以把这个指针强制转化为一个访问权限为一字节的指针,也就是char*类型

强制转化完后,再一个一个字节的交换,而交换的次数就是这个类型所占的字节数size

代码如下:

void Swap(void* p1, void* p2, int size)
{for (int i = 0; i < size; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}

cmp - 比较函数

我们需要排序本体也对多种数据进行排序,但是int类型比大小是比较数据的大小;字符串比大小是比字典序;外加我们有时候需要升序,有时候需要降序。如何让我们的排序在比大小的时候可以匹配任意规则呢?

为此,qsort的设计者将比较权限交给了使用者,也就是说用户可以自定义数据的比较规则。这是怎么做到的?

这得益于回调函数。用户可以把比较规则写在函数种,然后把函数当作参数传递给排序主体,这样qsort就知道如何排序了。这个函数一般叫做cmp即compare的缩写。

假设我们的待比较的元素是e1e2

首先,cmp给了一个基本规定:

  • 当 e1 > e2 返回值大于0
  • 当 e1 = e2 返回值等于0
  • 当 e1 < e2 返回值小于0

接下来我们讨论如何做一个回调函数的指针参数:

函数指针做参数有一个缺点,那就是指针类型是写死的。

如果用户要比较两个int类型数据,用户会这样写:int cmp(int x, int y);
此函数的指针类型为int (*)(int, int);

但是如果比较float的数据,那就会这样写:int cmp(float x, float y);
此函数的指针类型为int (*)(float, float);

函数指针类型不一样,这不利于统一处理函数。那么如何让cmp函数可以接收任何类型的数据呢?此时void*类型的指针又上场了,为了避免数据类型问题,我们干脆不传值了,直接传void*指针来统一处理数据。

于是qsort规定:所有人自己在写cmp函数的时候,两个待比较的参数必须是void*类型。返回值必须是int类型,以及: 当 e1 > e2 返回值大于0;当 e1 = e2 返回值等于0;当 e1 < e2 返回值小于0。

至此,我们就可以确定我们的回调函数指针类型就是int (*)(void*, void*)了,那实现cmp函数的人又要怎么办呢?

看到以下cmp函数:

int int_cmp(void* e1, void* e2)
{}

假设我们要比较两个int类型的数据,现在我们得到了两个void*类型的指针,分别指向两个数据,要如何比较呢?

答案当然是:先强制转化为int*类型的指针,然后再比较

比如这样:

int int_cmp(void* e1, void* e2)
{int ret = *(int*)e1 - *(int*)e2;return ret;
}

(int*)e1是在对void*类型的指针进行类型转化,转化完成后再解引用,所以是*(int*)e1e2同理。

qsort对返回值的规定,我们直接让两数相减即可。==如果你想排逆序,那么交换e1e2的位置即可:

int int_cmp(void* e1, void* e2)
{return 	*(int*)e2 - *(int*)e1;//逆序排序
}

qsort - 排序主体

我们原本的冒泡排序主体如下:

void qsort(int* arr,int sz)
{for (int i = 0; i < sz; i++){for (int j = 0; j < sz - i - 1; j++){if (arr[j] > arr[j + 1]){swap(&arr[j], &arr[j + 1]);}}}
}

由于传入的数据不一定是int类型,是不确定的,结合之前的经验,这里多半要传入一个void*类型的指针。

我们看看最终版本的qsort的参数:

void qsort(void* base, int nmemb, int size, int(*cmp)(const void*, const void*))
  • base,即传入的待排序数组,用void*接收,以适应不同类型数据
  • nmemb 这个数组的元素个数,防止越界访问
  • size 待排序的变量类型,所占用的字节数(Swap种需要知道这个变量占几个字节)
  • cmp 即用户自定义的比较函数,这里统一了类型为int (*)(void*, void*),用户使用时要用这种类型的函数

我们先前已经优化了用于比较的cmp函数,接下来就是把原来函数种比较的大于小于号,改成利用cmp函数比较

原本的比较:

if (arr[j] > arr[j + 1])

现在我们有指向数组的指针base,指针偏移量j,单个元素占用的字节数size,以及用于比较的函数cmp

首先要定位到当前j指向的元素,对于平时的访问,我们直接*(base + j)即可,但是我们的basevoid*类型的指针,不能解引用。与上一次相同,既然我们知道了一个变量占几个字节,我们干脆都统一为cahr*类型,一个一个字节来处理。

所以要先把base转化为char*(cahr*)base。随后跳过j个变量,一个变量占size个字节,那就需要跳过j * size个字节,因此目标元素的地址为:(char*)base + (j * size)

现在我们要往cmp函数里面传入jj + 1位置的元素的地址,所以cmp的调用为:

cmp((char*)base + j * size, (char*)base + ((j + 1) * size))

如果cmp的返回值大于0,则说明要交换,所以if语句如下:

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

如果满足要求,需要利用Swap交换,传参与cmp是同理的:

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

接下来看一下完整的代码:

void Swap(void* p1, void* p2, int size)
{for (int i = 0; i < size; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}void qsort(void* base, int nmemb, int size, int(*cmp)(const void*, const void*))
{for (int i = 0; i < nmemb - 1; i++){for (int j = 0; j < nmemb - i - 1; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0){Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}

当我们需要调用这个qsortdouble类型的数据进行排序,我们要写以下代码:

int cmp(const void* e1, const void* e2)
{return (*(double*)e1 - *(double*)e2);
}int main()
{double arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(double), cmp);return 0;
}

首先我们自己写一个double类型数据的比较规则:

int cmp(const void* e1, const void* e2)
{return (*(double*)e1 - *(double*)e2);
}

然后传参:qsort(数组指针, 数组长度, 该类型数据占用字节, 自己写的比较函数)

qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(double), cmp);

至此,你应该明白了为什么我们要这样写qsort函数,以后利用qsort进行排序,也就更清楚的知道每一步,每一个参数是干什么用的了。


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

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

相关文章

量子计算:数据安全难题

当今数字技术面临的最大挑战之一是安全系统和数据。为此&#xff0c;人们设计了复杂的算法来加密数据并通过称为对称加密的框架来保护数据。虽然这已被证明是成功的&#xff0c;但量子计算的进步&#xff08;利用量子力学比传统计算机更快地解决复杂问题&#xff09;可能会彻底…

【Pytorch 基础教程2】10分钟掌握Tensor基础 VSCode +Pytorch配置

Pytorch 基础教程 02 Tensor PyTorch 作为Numpy的代替品&#xff0c;可以使用GPU的强大计算能力 提供最大的灵活性和告诉的深度学习研究平台 这里补充上实验环境调试&#xff1a;第一次使用VS Code可以参考&#xff1a;PyTorch&#xff08;超详细&#xff09;部署与激活 举起Py…

优先队列C

由于看到P1629 邮递员送信这题,就去学了优先队列.为学习Dijkstra算法做准备 什么是优先队列 优先队列:是一种特殊类型的队列&#xff0c;每个元素都有一个相关的优先级。在优先队列中&#xff0c;元素按照优先级的顺序进行排列&#xff0c;具有最高&#xff08;或最低&#x…

Prometheus 教程

目录 一、简介二、下载安装1、安装 prometheus2、安装 alertmanager3、安装 grafana4、安装 node_exporter5、安装 mysqld_exporter 一、简介 Prometheus 是一个开源的系统监控和警报工具。它最初由 SoundCloud 开发&#xff0c;并于 2012 年发布为开源项目。Prometheus 专注于…

利用vite快速搭建vue3项目

1、选择一个文件夹&#xff0c;在vscode终端打开&#xff0c;输入命令【npm create vitelatest】 npm create vitelatest 2、提示你输入项目名称之后&#xff0c;我这里设置的是【rookiedemo】 3、回车之后&#xff0c;出现选择框架的提示&#xff0c;我们选择【vue】回车 4、…

js中使用for in注意事项,key的类型为string类型

for in是一个非常实用的存在&#xff0c;既可以遍历数组&#xff0c;又可以遍历对象&#xff0c;所以我一般都是会用来遍历可迭代的数据&#xff0c;遍历数组和对象的时候&#xff0c;要注意使用万能遍历方式&#xff1a; const users [1, 3, 45, 6]// const users {// 1…

Polyspace静态检测步骤

Polyspace 是一个代码静态分析和验证的工具&#xff0c;隶属于MATLAB&#xff0c;用于检测代码中的错误和缺陷&#xff0c;包括内存泄漏、数组越界、空指针引用等。帮助开发团队提高代码质量&#xff0c;减少软件开发过程中的错误和风险。 1、打开MATLAB R2018b 2、找到Polys…

AR智能眼镜主板硬件设计_AR眼镜光学方案

AR眼镜凭借其通过导航、游戏、聊天、翻译、音乐、电影和拍照等交互方式&#xff0c;将现实与虚拟进行无缝融合的特点&#xff0c;实现了更加沉浸式的体验。然而&#xff0c;要让AR眼镜真正成为便捷实用的智能设备&#xff0c;需要解决一系列技术难题&#xff0c;如显示、散热、…

Stable Diffusion 绘画入门教程(webui)-图生图

通过之前的文章相信大家对文生图已经不陌生了&#xff0c;那么图生图是干啥的呢&#xff1f; 简单理解就是根据我们给出的图片做为参考进行生成图片。 一、能干啥 这里举两个例子 1、二次元头像 真人转二次元&#xff0c;或者二次元转真人都行&#xff0c; 下图为真人转二次…

小清新卡通人物404错误页面源码

小清新卡通人物404错误页面源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 蓝奏云&#xff1a;https://wfr.lanzout.com/i6XbU1olftde

二叉树(6)——二叉树的创建和销毁

1 二叉树的创建 整体思路 将数组里的元素一直分为根&#xff0c;左子树&#xff0c;右子树&#xff0c;遇到#就返回NULL&#xff0c;链接到上层递归的左子树或者右子树&#xff0c;一定要把一个节点的左子树全部递归完才能返回到右子树。这种方法也可以scanf一个数组里的元素&…

Spring Boot项目怎么对System.setProperty(key, value)设置的属性进行读取加解密

一、前言 之前我写过一篇文章使用SM4国密加密算法对Spring Boot项目数据库连接信息以及yaml文件配置属性进行加密配置&#xff08;读取时自动解密&#xff09;&#xff0c;对Spring Boot项目的属性读取时进行加解密&#xff0c;但是没有说明对System.setProperty(key, value)设…

Mysql全局级别修改SQL模式的详细教程

文章目录 1. 问题描述2. 开发环境3. 解决方法&#xff08;详细步骤&#xff09; 1. 问题描述 Cause: java.sql.SQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column btc-cloud.t1.id which is not functiona…

socket与rpc的区别

如今的游戏开发&#xff0c;不搞个跨服玩法都不好意思说在做游戏了&#xff08;当然&#xff0c;也跟游戏类型有关&#xff0c;一些轻度休闲游戏可以排除在外&#xff09;。跨服玩法的设计&#xff0c;可以进一步激发玩家追求高战力的虚荣心&#xff0c;也可以汇聚玩家数量&…

Docker Compose映射卷的作用是什么,dockerfile这个文件有什么区别和联系?

Docker Compose中映射卷&#xff08;Volumes&#xff09;的作用和Dockerfile之间既有区别也有联系。下面详细解释两者的作用、区别和联系&#xff1a; Docker Compose映射卷的作用 在Docker Compose中&#xff0c;卷&#xff08;Volumes&#xff09;用于数据持久化和数据共享…

【实战 JS逆向+python模拟获取+Redis token会话更新】实战模拟测试 某巴批发网 仅供学习

逆向日期&#xff1a;2024.02.20 使用工具&#xff1a;Node.js、python、Redis 加密方法&#xff1a;md5标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标…

day07-实战-今日指数

今日指数-day07 1.股票Code联想推荐 1.1 股票Code联想推荐功能介绍 1) 原型效果 输入框输入股票编码后&#xff0c;显示关联的股票信息; 2&#xff09;接口定义说明 接口说明&#xff1a; 功能描述&#xff1a;根据输入的个股代码&#xff0c;进行模糊查询&#xff0c;返…

Python学习-流程图、分支与循环(branch and loop)

十、流程图 1、流程图&#xff08;Flowchart&#xff09; 流程图是一种用于表示算法或代码流程的框图组合&#xff0c;它以不同类型的框框代表不同种类的程序步骤&#xff0c;每两个步骤之间以箭头连接起来。 好处&#xff1a; 1&#xff09;代码的指导文档 2&#xff09;有助…

【Python】OpenCV-实时眼睛疲劳检测与提醒

实时眼睛疲劳检测与提醒 1. 引言 眼睛疲劳对于长时间使用电子设备的人群来说是一个常见的问题。为了帮助用户及时发现眼睛疲劳并采取相应的措施&#xff0c;本文介绍了一个实时眼睛疲劳检测与提醒系统的简单实现。使用了OpenCV、MediaPipe以及Playsound库&#xff0c;通过摄像…

云服务器ECS价格表出炉——阿里云

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…