qsort(冒泡排序)函数的模拟实现

目录

1、qsort 函数的基本介绍

1.1 qsort 的标准形式

1.2 qsort 的使用情况

1.3 qsort 中的比较函数

1.3.1 比较数字类型

1.3.2 比较字符串类型

2. 模拟实现 qsort 函数

2.1 自定义 bubble_sort 函数

2.2 bubble_sort 函数的内部结构

2.2.1 两个元素的比较

2.2.2 交换

3. 常用的比较函数举例 

3.1 整型比较函数

3.2 单精度浮点型比较函数

3.3 结构体类型比较函数


1、qsort 函数的基本介绍

1.1 qsort 的标准形式

我们先来看一下库函数 qsort 的标准形式

void qsort(void* base,
           size_t num, 
           size_t width, 
           int(*cmp)(const void* e1, const void* e2));

qsort 函数包含4个参数,无返回类型,使用时需要引头文件 <stdlib.h>。其中第一个参数 void* base 是待排序数组的首元素地址;第二个参数 size_t num 是待排序数组的元素个数;第三个参数 size_t num 是待排序数组的每个元素的大小,单位是字节;而第四个参数 int(*cmp)(const void* e1, const void* e2)) 是函数指针,比较两个元素的所用函数的地址,这个函数使用者自己实现,函数指针的两个参数是:待比较的两个元素的地址。

1.2 qsort 的使用情况

下面以排序浮点型数组来简单介绍 qsort 的使用情况,

#include <stdlib.h>
#include <string.h>

int cmp_float(const void* e1, const void* e2)
{
    return ((int)(*(float*)e1 - *(float*)e2));
}

int main()
{
    //给定单精度浮点型数组 f
    float f[] = { 9.0,8.0,5.0,4.0,7.0,2.0,6.0 };

    //用变量 sz 来存放数组的元素个数
    int sz = sizeof(f) / sizeof(f[0]);

    //其中 sizeof(f[0]) 表示数组每个元素的大小
    //这里的比较函数 cmp_float由我们自定义,需要使用者自己来实现
    qsort(f, sz, sizeof(f[0]), cmp_float);
    
    return 0;
}

1.3 qsort 中的比较函数

int cmp_float(const void* e1, const void* e2)

 实现比较函数时,有如下规定:
1.前一个元素大于后一个元素,返回大于0的数
2.前一个元素小于后一个元素,返回小于0的数
3.前一个元素等于后一个元素,返回数字0

下面介绍比较函数的基本情况。

1.3.1 比较数字类型

此时只需将 void* 类型的指针e1,e2强制类型转换为数组元素类型,对其解引用后再相减,最后将结果强制类型转化为 int 即可。如:

((int)(*(float*)e1 - *(float*)e2))

1.3.2 比较字符串类型

由于字符串不能直接用数学符号来比较,我们这里使用 strcmp 函数,而 strcmp 正好满足:

1.前一个元素大于后一个元素,返回大于0的数
2.前一个元素小于后一个元素,返回小于0的数
3.前一个元素等于后一个元素,返回数字0

故按照如下方法使用即可(这里以结构体指针指向其结构体成员 name 来举例):

strcmp(((struct Stu*)e2)->name, ((struct Stu*)e2)->name)

2. 模拟实现 qsort 函数

2.1 自定义 bubble_sort 函数

为了与 qsort 函数保持一致,我们给定 bubble_sort 函数,并设置相同的形式参数。

void bubble_sort(void* base, 
                 size_t sz, 
                 size_t width, 
                 int (*cmp)(const void* e1, const void* e2))

2.2 bubble_sort 函数的内部结构

由于每次都进行一趟比较,且每趟比较的对数都会递减1,故使用两层 for 循环结构

void bubble_sort(void* base, 
                 size_t sz,
                 size_t width, 
                 int (*cmp)(const void* e1, const void* e2))
{
    //进行比较的趟数
    int i = 0;
    for (i = 0; i < sz - 1; i++)
    {
        //每一趟比较的对数
        int j = 0;
        for (j = 0; j < sz-1-i; j++)
        {
            //...
        }
    }
}

下面,我们只需要在第二层 for 循环内部实现两个功能,第一个是将两个元素进行比较,第二个是交换。

2.2.1 两个元素的比较

由于比较函数在 qsort 已做出说明,故我们只需解决比较函数的传参问题。我们先来看参数类型,

int cmp(const void* e1, const void* e2)

这里的 e1,e2 都是无类型的不可修改的指针,进行元素比较时,我们需要让 e1,e2 准确的指向相邻的两个元素。但元素存在字符型、整型、浮点型、自定义类型等等,且每个类型在内存中的大小都不相同,我们如何才能将指针准确的指向相邻元素的首地址呢?

这里我们注意到,虽然每个类型在内存中所占的字节数不同,但字符类型只占一个字节,我们将 e1,e2 强制类型转换成 char* 后,仅需在后面加上 j*width ,就可以让指针指向每个元素的首地址。这里我们使用 if 进行判断,

                                                //指向前一个元素的后面一个
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
    //交换
}

2.2.2 交换

当指针 cmp 指向的比较函数的返回值大于0时,表示相邻的两个元素前一个大,故需要将两个元素进行交换。而返回值小于等于0时,无需交换。

下面将交换功能封装成函数 Swap ,由于每个类型所占字节数不同,故我们每次仅交换一个字节,交换元素大小的次数后,两个元素便实现了交换。

这里我们需要对 Swap 函数传递三个参数,

Swap((char*)base + j * width, (char*)base + (j + 1) * width, width)

前两个参数与比较函数相同,指向两个元素的首地址,第三个参数则是元素的大小。

由于交换函数较简单,下面直接给出交换函数的具体内容,

void Swap(char* buf1, char* buf2,int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}

至此,我们模拟实现的冒泡排序函数已经完成。

void Swap(char* buf1, char* buf2,int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}

void bubble_sort(void* base, 
                 size_t sz, 
                 size_t width, 
                 int (*cmp)(const void* e1, const void* e2))
{
    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((char*)base + j * width, (char*)base + (j + 1) * width, width);
            }
        }
    }
}

3. 常用的比较函数举例 

3.1 整型比较函数

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

3.2 单精度浮点型比较函数

int cmp_float(const void* e1, const void* e2)
{
    return ((int)(*(float*)e1 - *(float*)e2));
}

3.3 结构体类型比较函数

int cmp_stu_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
    //比较名字就是比较字符串
    //字符串比较不能直接用><=来比较,应该用strcmp函数
    return strcmp(((struct Stu*)e2)->name, ((struct Stu*)e2)->name);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

waywt1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值