C语言qsort函数的模拟实现

1.qsort函数

(1).概念

qsort函数是一个库函数,头文件为stdlib.h,用来排序的,可以排序任何类型的数据,它排序的底层原理是快速排序。

(2).函数定义

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

参数:

  1. void* base, //void*指针可以存放各种类型指针,指向需要排序数组首元素的地址
  2. size_t num, //数组元素的个数num,数组元素大于0,所以是size_t无符号类型;通常计算数组大小是使用sizeof(数组的地址)/sizeof(数组元素大小),但是这里只有数组首元素的地址,所以必须告诉qsort函数数组大小
  3.  size_t size,//数组元素的大小size(单位:字节),在排序时需要找到两个元素进行比较,有了元素的大小,就知道指针一次要访问多少个字节,就能准确找到元素。
  4. int (*compar)(const void*,const void*));//比较函数的函数指针 ,不同数据的比较方法不同比较方法,方法是需要我们自己来实现的,将判断方法封装成函数,再将函数的地址传给qsort函数,此外函数的返回值也要满足qsort函数给定的规定

 这个比较函数我们在实现时,返回值需要满足如果前面的>后面的,就返回大于0的数,就为真就会交换;等于就返回0,小于就返回小于0的数,就为假不会交换。

(3).函数的使用

A.qsort函数排序整型数组元素

数组元素升序,先来实现一下比较函数:

int int_compar(const void* e1, const void* e2)//整形比较
{
    return (*(int*)e1) - (*(int*)e2);//使用前先将void*指针转为int*,才能进行解引用
    //前者>后者,return > 0;//前者=后者,return = 0;//前者<后者,return < 0;
}

再来看下主程序:

#include<stdio.h>
#include<stdlib.h>
int int_compar(const void* e1, const void* e2)
{
    return (*(int*)e1) - (*(int*)e2);
}
int main()
{
    int arr[10] = { 1,2,3,7,6,99,23,34,54,65 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    //升序,需要首元素地址,长度,元素大小,比较函数指针
    qsort(arr, sz, sizeof(int), int_compar);
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

来看一下结果:

 

可见,已经帮我们排好序了,使用qsort函数更加方便,只需要写好比较函数就能排序了。

 B.qsort函数排序结构体

先来看下这个结构体:

struct Stu
{
    char name[100];
    int age;
};

这个结构体成员变量分别是名字和年龄,那么有两种就会有比较方式,比较名字和比较年龄

比较名字:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
    char name[100];
    int age;
};
//按照名字排序,比较字符
int char_compar(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    //字符串比较函数,头文件为string.h,返回条件与qsort所需条件相同
}
int main()
{
    struct Stu S[3] = { {"huangzhe",20},{"oujiahui",21},{"ouzhongmin",19} };
    int sz = sizeof(S) / sizeof(S[0]);
    //这里需要结构体数组首元素地址,结构总长度,结构体元素大小,字符比较函数
    qsort(S, sz, sizeof(S[0]), char_compar);
    for (int i = 0; i < sz; i++)
    {
        printf("%s,%d\n", S[i].name ,S[i].age);
    }
    return 0;
}

来看一下结果:

strcmp函数比较字符串是按照对应字符的ASCII码值进行比较,>0返回>0,<0返回<0,=0返回0。

 比较年龄:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
    char name[100];
    int age;
};
//按照年龄排序升序,比较整形
int int_compar(const void* e1, const void* e2)
{
    return (((struct Stu*)e1)->age) - (((struct Stu*)e2)->age);
}
int main()
{
    struct Stu S[3] = { {"huangzhe",20},{"oujiahui",21},{"ouzhongmin",19} };
    int sz = sizeof(S) / sizeof(S[0]);
    //这里需要结构体数组首元素地址,结构总长度,结构体元素大小,整形比较函数
    qsort(S, sz, sizeof(S[0]), int_compar);
    for (int i = 0; i < sz; i++)
    {
        printf("%s,%d\n", S[i].name, S[i].age);
    }
    return 0;
}

来看一下结果:

2冒泡排序模拟实现qsort函数

(1).冒泡排序

原理:

n个元素比较n-1轮,剩下的那个元素就不用比较了;每一轮两两相互比较,只需要比较n-1次,每比较一轮,就少一个数,第i轮只需要比较n-1-i次。

具体代码以及实现: 

#include<stdio.h>
void bubble_sort(int arr[10],int sz)
{
    int i, j;
    //排序
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}
int main()
{
    int arr[10] = { 5,6,3,4,1,8,9,1,2,7 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    //冒泡排序
    bubble_sort(arr, sz);
    //打印
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

结果:

可见已经升序。

(2).改进过程

参数: 

现在对这个冒泡排序进行修改,使得它能排序任何数据
根据qsort的实现原理结合bubble_sort函数的缺陷,可以发现面对的问题:

  • 1.只能排序整形数据,不能接受任意类型的数据
  • 解决方法:使用void*的指针接收需要排序的首元素的地址,void* base;
  • 存在问题:void*的指针不能进行解引用,加减运算 --> 导致不知道元素个数,不知道元素的类型
  • 2.要知道元素的个数
  • 解决方法:使用size_t类型接收元素个数,size_t sz;
  • 3.不知道元素的类型,就不知道指针一次读取几个字节,就找不到元素,无法比较
  • 解决方法:使用size_t类型接收元素所占字节数  size_t size;
  • 4.判断不同类型的数据的判断方法不同
  • 解决方法:将判断方法封装成函数,满足条件返回大于0的数(真),小于0返小于0,等于0返回等于0(<=0为假)
 那么新的函数定义:
void my_qsort(void* base,
               size_t sz,
             size_t size,
             int (*compar)(const void* e1,const void* e2));

结合新的函数定义以及新的参数对原来的bubble_sort函数的函数体进行分析:

发现问题:

1.if (arr[j] > arr[j + 1])//if的判断条件需要改
改后:if (compar(元素1的首地址,元素2的首地址))//调用判断函数
2.首元素的首地址为base,下一个元素的地址是什么?*****

  •  不就是首元素跳过一个元素,对应的就是地址跳过一个元素的字节数
  •  而元素的字节数就是size,那么首地址base只需要加上size就行了
  •  但是size是个数值,指针+数值==指针+数值*指针所指向的数据类型字节数
  •  那么我们就要保证指针所指向的数据类型字节数为1就行了,就只需要把void*的指针强制类型转换为char*

改后:

if(compar((char*)base,(char*)base+size))

3.数据交换这里也需要改*****
现在我们知道两个元素的首地址,和元素的大小,我们只需要将每个元素的每个字节的数值,进行交换就行

封装成函数后:

void Swap(char* p1, char* p2,size_t size)
{
    int n;
    for (n = 0; n < size; n++)//每个字节进行交换
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;//交换之后地址加1,来到下一个字节的地址
        p2++;
    }
}

最后改完的bubble_sort函数得到我们自己的my_qsort函数:

改后代码:
void Swap(char* p1, char* p2,size_t size)
{
    int n;
    for (n = 0; n < size; n++)
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2++;
    }
}
void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2))
{
    int i, j;
    //排序
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环
        {
            if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改
            {
                //数据交换函数
                Swap((char*)base + size * j, (char*)base + size * (j + 1), size);
            }
        }
    }
}

(3).函数的使用

最后对我们的my_qsort函数进行使用验证:

A.qsort函数排序整型数组元素

代码:
#include<stdio.h>
void Swap(char* p1, char* p2,size_t size)
{
    int n;
    for (n = 0; n < size; n++)
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2++;
    }
}
void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2))
{
    int i, j;
    //排序
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环
        {
            if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改
            {
                //数据交换函数
                Swap((char*)base + size * j, (char*)base + size * (j + 1), size);
            }
        }
    }
}
//整形比较函数
int int_compar(const void* e1, const void* e2)
{
    return (*(int*)e1) - (*(int*)e2);
}
int main()
{
    int arr[10] = { 5,6,3,4,1,8,9,1,2,7 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    my_qsort(arr, sz, sizeof(arr[0]), int_compar);
    //打印
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}
来看一下结果:

 成功了!!!!!!

 B.qsort函数排序结构体

先直接看结果:
比较名字:
成功了!!!!!!!
比较年龄:
成功了!!!!!!!
代码:
比较名字:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
    char name[100];
    int age;
};
void Swap(char* p1, char* p2, size_t size)
{
    int n;
    for (n = 0; n < size; n++)
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2++;
    }
}
void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2))
{
    int i, j;
    //排序
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环
        {
            if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改
            {
                //数据交换函数
                Swap((char*)base + size * j, (char*)base + size * (j + 1), size);
            }
        }
    }
}
//按照名字排序,比较字符
int char_compar(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int main()
{
    struct Stu S[3] = { {"oujiahui",21},{"huangzhe",20},{"ouzhongmin",19} };
    int sz = sizeof(S) / sizeof(S[0]);
    //
    my_qsort(S, sz, sizeof(S[0]), char_compar);
    for (int i = 0; i < sz; i++)
    {
        printf("%s,%d\n", S[i].name, S[i].age);
    }
    return 0;
}
比较年龄:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
    char name[100];
    int age;
};
void Swap(char* p1, char* p2, size_t size)
{
    int n;
    for (n = 0; n < size; n++)
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2++;
    }
}
void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2))
{
    int i, j;
    //排序
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环
        {
            if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改
            {
                //数据交换函数
                Swap((char*)base + size * j, (char*)base + size * (j + 1), size);
            }
        }
    }
}
//按照年龄排序升序,比较整形
int int_compar(const void* e1, const void* e2)
{
    return (((struct Stu*)e1)->age) - (((struct Stu*)e2)->age);
}
int main()
{
    struct Stu S[3] = { {"oujiahui",21},{"huangzhe",20},{"ouzhongmin",19} };
    int sz = sizeof(S) / sizeof(S[0]);
    
    my_qsort(S, sz, sizeof(S[0]), int_compar);
    for (int i = 0; i < sz; i++)
    {
        printf("%s,%d\n", S[i].name, S[i].age);
    }
    return 0;
}

验证结束了,是正确的呀!!!!!!


本章内容结束了,我们下章见,拜拜!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值