深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数

一.模拟实现函数参数

首先我们打开 cplusplus 官网查看 qsort 的参数要求和含义

qsort - C++ Reference (cplusplus.com)

具体参数信息如下

void* base:待排序数组的第一个元素的地址

size_t num:待排序数组的元素个数

size_t size:待排序数组中一个元素的大小

int (*compar)(const void*, const void*):函数指针-cmp指向了一个函数,这个函数是用来比较两个元素的,e1和e2中存放的是需要比较的两个元素的地址

根据上面的定义说明,我们对函数参数进行模拟

void my_qsort(void* base, size_t num, size_t size, int (*compare)(const void* e1, const void*

二.确定算法大体的框架

       正如标题所说,笔者这里使用较为容易理解的冒泡排序,当然其他的快速排序选择排序堆排序希尔排序等算法都是可以使用的,笔者这里只是为了方便讲解具体实现流程和细节,所以选择的冒泡排序的算法

  在选择好了使用什么算法进行模拟实现后,我们就需要来设计大体的框架了,首先,我们列出一般冒泡排序的形式,然后在这基础上进行删改

  for (int i = 0; i < num - 1; i++)
  {
    //每一趟
    for (int j = 0; j < num - i - 1; j++)
    {
      //排序:前一个元素大于后一个就进行交换
      if (arr[j] > arr[j + 1])
      {
        int temp = 0;
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }

在这里,我们需要修改的地方主要是俩点


判断部分,考虑到不同数据的复杂类型,判断部分不能再使用这样简单数组的方式进行判断,应该设计成一个函数指针,针对不同的数据类型调用不同的函数来进行不同的判断

交换部分 ,考虑不同的数据的复杂类型,交换部分也不能使用 int 型的临时变量来进行交换数据,应该设计成对任意数据类型都能交换的方式,也就是对字节直接进行操作,对于不同的数据类型,我们直接对其内存存储的字节进行交换,这样就能达到目的和要求

三.实现函数的判断逻辑、

函数指针部分:

       首先,为了实现不同的判断方法,我们要有一个函数指针去调用不同的判断方法,我们根据 qsort 的参数要求设计如下函数指针


这个函数指针有俩个参数,分别代表着需要判断排序的数据,我们调用的参数的返回值是有要求的,如下图

那我们就可以将这个判断逻辑的函数的返回值来作为 if 语句判断依据,实现实例如下

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

   这代表着,如果判断函数返回值大于0,那我们就执行交换操作,如果等于0或者小于0就不执行交换操作,那我们实现了函数调用的接口,我们还得具体的实现判断逻辑函数,方便进行调用

具体判断方法函数:

       根据我们在上卷文章中讲解的判断逻辑和实现方法,我们可以同样试用在这里,具体细节如有不懂可以访问下面的链接

深入解析 qsort 排序(上),它为什么是万能排序?

//比较函数--整形
int comp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
//比较函数--浮点型
int comp_float(const void* a, const void* b)
{
  return *(float*)b - *(float*)a;
}
//比较函数--按照年龄
int comp_age(const void* a, const void* b)
{
  return ((struct stu*)b)->age - ((struct stu*)a)->age;
}
//比较函数--按照姓名
int comp_name(const void* a, const void* b)
{
  return strcmp(((struct stu*)a)->name, ((struct stu*)b)->name);
}

四.交换函数的实现

       首先,我们要确定我们要进行交换的对象,对于冒泡排序来说,需要交换的只是前后俩个元素,那我们拿到他们的地址就可以进行交换了,我们具体实现如下

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

这里分别对三个参数进行一下解释:


  1. (char*)base + j * size :确定要交换的第一个数据的地址
  2. (char*)base + (j + 1) * size :确定要交换的第二个数据的地址
  3. size :确定这俩个数据直接隔了多少个字节,方便逐字节进行操作

       对于参数为什么这样设定,有小伙伴可能不理解,下面笔者就以 int 类型举例,我们知道,int 类型占 4 个字节,而 char* 指针解引用可以访问一个字节,而数据在内存中存储的最小字节都是 1 个字节那我们就可以 通过 char* 指针来逐个访问字节,就像下面这张图一样,我们逐个交换俩个数据中的每一个字节,在交换完成后,我们就相当于把这俩个数字进行了交换

       在逐个交换字节后,宏观上给我们的感受就是直接交换了我们的数据内容,也就是说我们使用俩个 char* 指针分别访问要交换的俩个在数据,每一次交换一个字节后,指针加一,我们就可以继续访问交换后面的字节,在这里循环往复交换 size 个字节后,这俩个数据就被我们完全交换了


代码实现:

       注意,在具体写代码的过程中不要非法访问空间,在下方代码的注释部分,笔者对错误代码进行了注释,大家在写过程中一定得注意,在指针访问一些只读区域或者不是我们申请的空间的区域就会出现段错误就像注释部分的代码,我们定义一个空指针,然后再对这个空指针进行解引用访问赋值的操作就会出现报错

//交换函数
void swap(char* buf1, char* buf2, size_t size)
{
  //逐个字节进行交换,一共有size个字节
  for (int i = 0; i < size; i++)
  {
    //错误示范
    //char* temp = 0;
    //*temp = *buf1;
    //*buf1 = *buf2;
    //*buf2 = *temp;
    //buf1++;
    //buf2++;
    //正确示范
    char temp = *buf1;
    *buf1 = *buf2;
    *buf2 = temp;
    buf1++;
    buf2++;
  }
}

五.完整功能测试

       我们仍然使用在上篇文章中的用例进行测试,观察我们设计的函数能不能完成我们预期的功能,在这里对整形数组,浮点型数组,结构体数组进行测试

int main()
{
  int arr1[] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr1) / sizeof(arr1[0]);
  print1(arr1);
  my_qsort(arr1, sz, sizeof(int), comp_int);
  print1(arr1);
  float arr2[5] = { 1.2,3.4,5.6,7.8,9.9 };
  print2(arr2);
  my_qsort(arr2, 5, sizeof(float), comp_float);
  print2(arr2);
  struct stu arr3[] = { {"zhangsan",19},{"lisi",20},{"wangswu",21} };
  my_qsort(arr3, 3, sizeof(arr3[0]), comp_name);
  my_qsort(arr3, 3, sizeof(arr3[0]), comp_age);
  system("pause");
  return 0;
}

观察结果:

       我们可以发现和我们预期的一样,对于不同数据类型,我们自己模拟的函数都可以实现排序功能

六.完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<windows.h>
//结构体
struct stu
{
  char name[20];
  int age;
};
void print1(int* arr)
{
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", *(arr + i));
  }
  printf("\n");
}
void print2(float* arr)
{
  for (int i = 0; i < 5; i++)
  {
    printf("%.2f ", *(arr + i));
  }
  printf("\n");
}
//比较函数--整形
int comp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
//比较函数--浮点型
int comp_float(const void* a, const void* b)
{
  return *(float*)b - *(float*)a;
}
//比较函数--按照年龄
int comp_age(const void* a, const void* b)
{
  return ((struct stu*)b)->age - ((struct stu*)a)->age;
}
//比较函数--按照姓名
int comp_name(const void* a, const void* b)
{
  return strcmp(((struct stu*)a)->name, ((struct stu*)b)->name);
}
//交换函数
void swap(char* buf1, char* buf2, size_t size)
{
  //逐个字节进行交换,一共有size个字节
  for (int i = 0; i < size; i++)
  {
    //错误示范
    //char* temp = 0;
    //*temp = *buf1;
    //*buf1 = *buf2;
    //*buf2 = *temp;
    //buf1++;
    //buf2++;
    //正确示范
    char temp = *buf1;
    *buf1 = *buf2;
    *buf2 = temp;
    buf1++;
    buf2++;
  }
}
//void qsort(void* base, size_t num, size_t size,int (*compar)(const void*, const void*));
void my_qsort(void* base, size_t num, size_t size, int (*compare)(const void* e1, const void* e2))
{
  //使用冒泡排序
  for (int i = 0; i < num - 1; i++)
  {
    //每一趟
    for (int j = 0; j < num - i - 1; j++)
    {
      //排序:前一个元素大于后一个
      if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
      {
        //交换
        swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
      }
    }
  }
}
int main() 
{
  int arr1[] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr1) / sizeof(arr1[0]);
  print1(arr1);
  my_qsort(arr1, sz, sizeof(int), comp_int);
  print1(arr1);
  float arr2[5] = { 1.2,3.4,5.6,7.8,9.9 };
  print2(arr2);
  my_qsort(arr2, 5, sizeof(float), comp_float);
  print2(arr2);
  struct stu arr3[] = { {"zhangsan",19},{"lisi",20},{"wangswu",21} };
  my_qsort(arr3, 3, sizeof(arr3[0]), comp_name);
  my_qsort(arr3, 3, sizeof(arr3[0]), comp_age);
  system("pause");
  return 0;
}



  本次分享的俩篇文章就到此为止啦,希望我的分享对您有所帮助,文章中如有错误,欢迎积极指正,您的认可就是我最大的动力,那我们下次分享再见

目录
相关文章
|
2月前
|
SQL 数据挖掘 测试技术
南大通用GBase8s数据库:LISTAGG函数的解析
南大通用GBase8s数据库:LISTAGG函数的解析
|
1月前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
48 5
|
2月前
|
机器学习/深度学习 自然语言处理 语音技术
揭秘深度学习中的注意力机制:兼容性函数的深度解析
揭秘深度学习中的注意力机制:兼容性函数的深度解析
|
4月前
|
存储 前端开发 JavaScript
前端基础(十二)_函数高级、全局变量和局部变量、 预解析(变量提升)、函数返回值
本文介绍了JavaScript中作用域的概念,包括全局变量和局部变量的区别,预解析机制(变量提升),以及函数返回值的使用和类型。通过具体示例讲解了变量的作用域、函数的返回值、以及如何通过return关键字从函数中返回数据。
29 1
前端基础(十二)_函数高级、全局变量和局部变量、 预解析(变量提升)、函数返回值
|
3月前
|
存储
atoi函数解析以及自定义类型经典练习题
atoi函数解析以及自定义类型经典练习题
65 0
|
3月前
|
搜索推荐
冒泡排序(Bubble Sort)以及选择排序(Selection Sort)和快速排序(Quick Sort)详细解析
冒泡排序(Bubble Sort)以及选择排序(Selection Sort)和快速排序(Quick Sort)详细解析
40 1
|
3月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
深入探索:Python中的并发编程新纪元——协程与异步函数解析
33 3
|
3月前
|
搜索推荐 算法 数据可视化
深入解析冒泡排序算法
深入解析冒泡排序算法
57 4
|
3月前
|
机器学习/深度学习 算法 C语言
【Python】Math--数学函数(详细附解析~)
【Python】Math--数学函数(详细附解析~)
|
3月前
|
安全 编译器 C++
【C++篇】C++类与对象深度解析(三):类的默认成员函数详解
【C++篇】C++类与对象深度解析(三):类的默认成员函数详解
33 3

推荐镜像

更多
下一篇
开通oss服务