使用冒泡排序模拟qsort

news/2025/3/14 18:19:05/

目录

冒泡排序🐒:

冒泡排序特点👀:

模拟&改造🔧:

1、让冒泡排序能够接受其他的数据类型,使用参数的改造。🚗

2、比较的方式进行改造❤

思路分析🧠:

 3、交换的代码需要改造🏃‍

 cmp的回调👅:

🕵️‍最终代码演示: 


冒泡排序🐒:

http://t.csdn.cn/HQfDO      

冒泡排序特点👀:

1、利用数组中的元素进行俩两比较,和循环的多次遍历进行排序,得到一个升序或者降序的排列顺序。


2、但是效率过于底下,以及通过qsort的对比,冒泡排序只能用于整型(int)数组的比较和排列,对于其他数据类型的数组比较,显得无能为力。


3、而且,效率底下,运算需要进行多次的遍历,需要设定另外的变量值,进行急刹车,才能避免不必要的遍历循环。

模拟&改造🔧:

1、让冒泡排序能够接受其他的数据类型,使用参数的改造。🚗

  • int arr[] ——void*base  利用void*接收任意类型的地址
  • int sz ——size_t sz  因为void* 所以要有sz进行元素大小的判定,以便接下来移动指针
  • 增加 size_t width   因为void* 所以要有width得知每个元素的字节大小,以便接下来移动指针
  • 增加int(*cmp)(const void*,constvoid*) 使用comaprt 进行函数回调,一次进行两个元素之间的大小比较
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void*el, const void*e2))

2、比较的方式进行改造❤

原先的比较方式:if (arr[j] > arr[j + 1])


  • 原本的比较方式是基于整型数组的原理上,进行前后元素的比较。 
  • 现在,数据的类型发生了改变,变成了void*这种通用类型。
  • 站在程序员的角度,程序员是不明白用户在传输时究竟是哪一种的数据类型,所以不能直接指定某一种数据类型进行比较
  • 这是一个 if  语句,而compart的判断原理是两个数字之间的比较返值。 
  • 需要交换的条件究竟是什么?是需要升序?还是需要降序?

 

if(cmp((char*)base+j*width,(char*)base +(j+1)*width) > 0)

思路分析🧠:


if(cmp()>0) 其实是利用了cmp的返回值原理   

  • return(前一个元素   减    后一个元素)
  • 返回值大于0 则表示前一个元素大于后一个元素
  • 返回值小于0 则表示前一个元素小于后一个元素
  • 返回值等于0 则表示两个元素的数值是相等的

♥♥(char*)base+j*width  

  •     前文说过,站在程序员的角度,我们是不知晓用户传输的数据类型,但是对于char*来说有一个特有的优势。
  •     char*在进行访问字节的过程中,char*base+1 是只能访问一个字节的,而相对于其他,列如:int*base+1 是一次访问了四个字节。
  •     对于char*一次只能访问一个字节的特点,我们可以利用width以及下标 j 相结合组合成一个适合char*能够从首元素地址出发,指向某个元素的地址,的运算方式。

 

 3、交换的代码需要改造🏃‍


原先交换的代码:

                              int tmp = arr[j];

                              arr[j] = arr[j + i];

                              arr[j + 1] = tmp;


  • 原先交换的代码是基于整型数组的原理上进行的。
  • 交换时,我们的指针类型是否需要进行改变?若不需要进行再次的转变,那么该如何进行交换?
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

在面对交换问题的同时,为了简化或是说将整个代码进行简洁化,我们采用了函数调用的方式,以此解决问题。 

void Swap(char* buf1, char* buf2, size_t width)
{int i = 0;for (i = 0; i < width; i++)//利用遍历,进行字节和字节之间的交换{char tmp = *bufl;*buf1 = *buf2;*buf2 = tmp;buf1++:buf2++;}}

 还是引用前文的话,站在程序员的角度,我们无法知晓用户传输的数据类型究竟是什么,所以在此处无法进行数据类型的转化,所以还是使用char*进行交换。


而char*在前文也有提过,是char*base+1只能访问一个字节,所以我们可以利用循环的遍历,将宽度作为循环的最大次数,以此来进行字节和字节之间的交换,直到抵达相对应的字节大小(宽度)。

 cmp的回调👅:

int cmp(const void* e1, const void* e2)
{return *(int*)el - *(int*)e2;
}
  •  因为,cmp 在 qsor的 int  (*cmp)(const void*,const void*) 以及 在前文的 if 判定返值中必须是int整型类型的。
  • 也同时,cmp 是单独独立的一个函数调用,是独立的函数,进行修改时,并不会涉及其他函数的使用,所以此处便可直接进行指针数据类型的转化。
  • 最后,如果要更换数据类型进行比较,也只需要修改主函数的内容和cmp的回调内容即可。

🕵️‍最终代码演示: 

void Swap(char* buf1, char* buf2, size_t width)//交换
{int i = 0;for (i = 0; i < width; i++)//利用遍历,进行字节和字节之间的交换{char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}}
void BB(void* base, size_t sz, size_t width, int(*cmp)(const void*, const void*))//冒泡排模拟qsort
{int i = 0;for (i = 0; i < sz - 1; i++)//i表示排序的趟数,和冒泡排序一样的意思{int j = 0;for (j = 0; j < sz - 1 - i; j++)//j表示下摆,和冒泡排序一样的意思{if (cmp((char*)base + j * width,(char*)base + (j + 1) * width) > 0)//接收返回值{Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}int cmp(const void*e1 , const void* e2)//进行返回值
{return *(int*)e1 - *(int*)e2;
}
void print(int* arr, int sz)//打印
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
void test1()
{int arr[] = { 3,1,5,2,4,8,7,9,10,11,0 };int sz = sizeof(arr) / sizeof(arr[0]);BB(arr, sz, sizeof(arr[0]), cmp);//进行比较的函数,cmp回调print(arr, sz);//打印数组
}
int main()
{test1();return 0;
}


http://www.ppmy.cn/news/1036922.html

相关文章

SQLite数据库实现数据增删改查

当前文章介绍的设计的主要功能是利用 SQLite 数据库实现宠物投喂器上传数据的存储&#xff0c;并且支持数据的增删改查操作。其中&#xff0c;宠物投喂器上传的数据包括投喂间隔时间、水温、剩余重量等参数。 实现功能&#xff1a; 创建 SQLite 数据库表&#xff0c;用于存储宠…

前端JavaScript企业框架的全面解析

引言 在现代Web开发中&#xff0c;前端JavaScript框架扮演着至关重要的角色。它们提供了丰富的功能和工具&#xff0c;帮助开发人员构建功能强大且易于维护的企业级应用程序。本篇博客将全面解析前端JavaScript企业框架&#xff0c;介绍其优势、使用场景和常见的框架选择。 什…

力扣初级算法(数组拆分)

力扣初级算法&#xff08;数组拆分&#xff09; 每日一算法&#xff1a; 力扣初级算法&#xff08;数组拆分&#xff09; 学习内容&#xff1a; 1.问题描述 给定长度为 2n 的整数数组 nums &#xff0c;你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) …

Java基础(十三)面向对象编程 OOP

Java面向对象基础知识笔记&#xff08;二&#xff09; 1. this关键字 this 关键字代表当前对象的引用&#xff0c;可以用于访问当前对象的成员变量、成员方法和构造方法。在以下情况下常用到 this&#xff1a; 使用 this 调用成员变量&#xff0c;解决成员变量与局部变量的同…

第八章 CUDA内存应用与性能优化篇(上篇)

cuda教程目录 第一章 指针篇 第二章 CUDA原理篇 第三章 CUDA编译器环境配置篇 第四章 kernel函数基础篇 第五章 kernel索引(index)篇 第六章 kenel矩阵计算实战篇 第七章 kenel实战强化篇 第八章 CUDA内存应用与性能优化篇 第九章 CUDA原子(atomic)实战篇 第十章 CUDA流(strea…

Spring学习笔记之Bean的“出生入死”

文章目录 什么是Bean的生命周期为什么要知道Bean的生命周期Bean的生命周期之五个阶段Bean生命周期之七个阶段Bean生命周期的十个阶段Bean的作用域不同&#xff0c;管理方式不同自己new的对象如何让Spring管理 什么是Bean的生命周期 Spring其实就是一个管理Bean对象的工厂。它负…

Java多线程(4)---死锁和Synchronized加锁流程

目录 前言 一.synchronized 1.1概念 1.2Synchronized是什么锁&#xff1f; 1.3Synchronized加锁工作过程 1.4其他优化操作 二.死锁 2.1什么是死锁 2.2死锁的几个经典场景 2.3死锁产生的条件 2.4如何解决死锁 &#x1f381;个人主页&#xff1a;tq02的博客_CSDN博客…

前端跨域的原因以及解决方案(vue),一文让你真正理解跨域

跨域这个问题,可以说是前端的必需了解的,但是多少人是知其然不知所以然呢&#xff1f; 下面我们来梳理一下vue解决跨域的思路。 什么情况会跨域&#xff1f; ​ 跨域的本质就是浏览器基于同源策略的一种安全手段。所谓同源就是必须有以下三个相同点&#xff1a;协议相同、域名…