百独托管7500 紫田网络超高转化播放器收cps[推荐]速盾CDN 免实名免备防屏蔽阿里云 爆款特卖9.9元封顶提升alexa、IP流量7Q5团队
【腾讯云】中小企福利专场【腾讯云】多款产品1折起高防 随时退换 好耶数据小飞国外网赚带你月入万元炎黄网络4H4G10M 99每月
香港带宽CN2/美国站群优惠中客数据中心 服务器租用联盟系统移动广告平台 中易企业专场腾讯云服务器2.5折九九数据 工信部正规资质
腾讯云新用户大礼包代金券高价收cpa注册量高价展示【腾讯云】2核2G/9.93起租服务器找45互联 随时退换阿里云 短信服务 验证秒达

[闲聊畅谈] C语言-qsort函数详解 [复制链接]
查看:612 | 回复:0

1099

主题

3121

帖子

1057

积分

落伍者(两全齐美)

Rank: 2

贡献
159
鲜花
1
注册时间
2005-10-17

落伍手机绑定

发表于 2021-9-3 17:12:38 | 显示全部楼层 |阅读模式 来自 中国湖北
一.qsort函数是什么
我们可以使用  搜索库函数网址或者MSDN软件进行查找。

qsort()函数:快速排序的函数  -引用stdlib.h头文件



参数说明:
void qsort (

    void* base, //要排序的目标数组
    size_t num,     //待排序的元素个数
    size_t width,    //一个元素的大小,单位是字节
    int(*cmp)(const void* e1, const void* e2)

);        

其中cmp是函数指针,cmp指向的是:排序时,用来比较两个元素的函数。需要自己编写。

返回值:

        

二.使用qsort排序-以升序为例
关于void*型指针:
  void*:无具体类型的指针   能够接收任意类型的地址
缺点:不能进行运算。不能+-整数,不能解引用

int a  = 0;
float f = 5.5f;
void* p1 = &a;
void* p2 = &f;
p1 = p1+1;    //err
1.整形数组排序
注意:

1.比较函数的参数类型为void* ,我们要进行强制类型转换!且要解引用才能得到对应的值!

2.若我们想排成降序,只需要写成e2-e1即可

void Print(int* arr, int sz)
{
        int i = 0;
        for (i = 0; i < sz; i++)
        {
                printf("%d ", *(arr + i));
        }
        printf("\n");
}
//比较整形
//注意类型时void* 所以要强制类型转化,还要解引用才是对应的值!!!
int cmp_int(const void* e1, const void* e2)
{
        return *(int*)e1 - *(int*)e2;
}
void test1()
{
        int arr[] = { 9,8,7,6,7,5,4,8 };
        int sz = sizeof(arr) / sizeof(arr[0]);
        qsort(arr, sz, sizeof(arr[0]), cmp_int);
        Print(arr, sz);
}
2.字符数组排序
注意使用sizeof()操作符和strlen()函数的区别

//注意要要强制类型转换!! 要解引用!!!  本质上是比较Ascii值
int cmp_char(const void* e1, const void* e2)
{
    return *(char*)e1 - *(char*)e2;
}
void test4()
{
        char arr[] ="mango";
    //若使用sizeof计算长度:
        //int sz = sizeof(arr) / sizeof(arr[0]);        //6
        //qsort(arr, sz-1, sizeof(arr[0]), cmp_float);
    //因为sizeof把\0也算进去了,所以计算出来的值比字符串本身长度多1
   
    int sz = strlen(arr);        //5
    qsort(arr, sz, sizeof(arr[0]), cmp_char);
        printf("%s\n",arr);
}
3.字符指针数组排序
先看看下面这段程序有没有问题?

int cmp_chars(const void* e1, const void* e2)
{
        return strcmp((char*)e1, *(char*)e2);
}
void test2()
{
         char* arr1 = "abc";
         char* arr2 = "wcad";
         char* arr3 = "cab";
         char* p[3] = { arr1,arr2,arr3 };
        int sz = sizeof(p) / sizeof(p[0]);
        qsort(p, sz, sizeof(p[0]), cmp_chars);
        int i = 0;
        for (i = 0; i < sz; i++)
        {
                printf("%s\n", p);
        }
}
打印出来发现:结果是错误的!



->调试后发现:e2存放的是p的地址(char**类型),e1存放的是p指向的下一个元素的地址(char**类型)        

对于这种写法,传进去的是p的地址,strcmp()会将p地址对应的内容转化成字符串,也就是将p中arr1,arr2,arr3的地址转化成字符串

实际上应该传p地址空间中arr1,arr2的地址,这样strcmp()才能找到arr1和arr2对应的字符串,因此得先把e1,e2转化成char**,这样解引用以后才是一个char*的地址

原因:把p传给qsort,p是数组名->首元素地址,元素类型为char*>,所以p的类型为:char**类型。  所以e1 和e2也要强制类型转化为char**,解引用e1,e2才是对应字符串的地址!



正解:

int cmp_chars(const void* e1, const void* e2)
{
        return strcmp(*(char**)e1, *(char**)e2);
}
void test2()
{
         char* arr1 = "abc";
         char* arr2 = "wcad";
         char* arr3 = "cab";
         char* p[3] = { arr1,arr2,arr3 };
        int sz = sizeof(p) / sizeof(p[0]);
        qsort(p, sz, sizeof(p[0]), cmp_chars);
        int i = 0;
        for (i = 0; i < sz; i++)
        {
                printf("%s\n", p);
        }
4.结构体数组排序
比较年龄->实际比较的是整形

比较名字->实际比较的是字符串->使用strcmp函数,不能使用 == 判断

struct Stu
{
        int age;
        char name[20];
};
//比较结构体中元素的年龄
int cmp_age(const void* e1, const void* e2)
{
        //本质是比较整形
        return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//比较名字
int cmp_name(const void* e1, const void* e2)
{
        //本质是字符串比较->使用strcmp函数
        return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test2()
{
        //创建结构体数组,用大括号初始化
        struct Stu s[3] = { {19,"Mango"},{18,"Lemon"},{20,"Hello"} };
        int sz = sizeof(s) / sizeof(s[0]);
        //以年龄排
        qsort(s, sz, sizeof(s[0]), cmp_age);
        printf("%s %d ",s[0].name,s[0].age);
        printf("%s %d ", s[1].name, s[1].age);
        printf("%s %d ", s[2].name, s[2].age);
        printf("\n");
        //以姓名排
        qsort(s, sz, sizeof(s[0]), cmp_name);
        printf("%s %d ", s[0].name, s[0].age);
        printf("%s %d ", s[1].name, s[1].age);
        printf("%s %d ", s[2].name, s[2].age);
        printf("\n");
}
5.浮点型数组排序
注意:比较函数中,返回类型是int,最后相减的值要强制类型转化为int ,但这也会造成错误,建议使用方法2.

//写法1:可能会出错
// 原因: 0.2 -0.1 = 0.1 强制类型转化为int后 结果为0
//int cmp_float(const void* e1, const void* e2)
//{
//        //返回类型是int  所以相减后的结果要强制类型转化
//        return (int)(*(float*)e1 - *(float*)e2);
//}

//写法2:对应上qsort的返回值
int cmp_float(const void* e1, const void* e2)
{
        if ((*(float*)e1 - *(float*)e2) > 0.00000)
                return 1;
        else if ((*(float*)e1 - *(float*)e2) == 0.000000)
                return 0;
        else
                return -1;
}
void test3()
{
        float arr[5] = { 5.01f,5.01f,0.02f,0.01f,5.001f };
        int sz = sizeof(arr) / sizeof(arr[0]);
        qsort(arr, sz, sizeof(arr[0]), cmp_float);
        int i = 0;
        for (i = 0; i < sz; i++)
        {
                printf("%f ", arr);
        }
}
三.使用冒泡排序思想模拟实现qsort函数
1.什么是冒泡排序:


主要思想:相邻的两个元素进行比较



对于冒泡排序: n个元素 共进行n-1趟冒泡排序。一趟可以使一个元素在特定位置上,每趟排序可以少比较一个元素

但是冒泡排序只能排序整形

2.冒泡排序代码
void BubbleSort(int* arr, int sz)
{
        int i = 0;
        int j = 0;
        //共进行sz-1趟
        for (i = 0; i < sz-1; i++)
        {
                int flag = 1;//每一趟进来都假设有序
        // 每一趟
                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;
                                flag = 0;
                        }
                }
        //若falg还是1,说明没有交换->已经有序了break退出
                if (flag == 1)
                {
                        break;
                }
        }
}
int main()
{
        int arr[10] = { 2,3,6,7,9,0,0,3,2,10 };
        int sz = sizeof(arr) / sizeof(arr[0]);
        BubbleSort(arr, sz);
        return 0;
}
3. 使用冒泡排序思想模拟实现qsort函数
qsort库函数使用的是什么参数,我们设计的函数就使用什么参数!

  

1.为何将base强制类型转化为char*型指针:

原因:char* 指针+1跳过一个字节,+width:跳过width个字节,指向下一个元素。转化为其他类型不合适

2. 交换函数:还要把宽度(每个元素所占字节数)传过去
因为交换的时候是传地址,所以要知道元素的宽度,一个字节一个字节的交换 ,这样也证明了使用char*指针的好处!

3.(char*)base + j * width, (char*)base + (j + 1) * width,

  当j = 0时:比较的是第一个元素和第二个元素
   j = 1时,比较的是第二个元素和第三个元素
    ....  很妙的写法

//交换 --一个字节一个字节的交换,共交换width次
void Swap(char* buf1, char* buf2, size_t width)
{
        size_t i = 0;
        for (i = 0; i < width; i++)
        {
                char tmp = *buf1;
                *buf1 = *buf2;
                *buf2 = tmp;
                buf1++;
                buf2++;
        }
}
void my_BubbleSort(void* base, size_t num,size_t width, int(*cmp)(const void* e1, const void* e2))
{
        //冒泡排序
        //若要排序n个元素,只需要进行n-1趟
        //每一趟可以少比较一个元素,每一趟可以使一个元素在确定的位置上
        //num:要排序元素的个数 类型是size_t
    //num是无符号数 防止产生警告 所以i和j也定义为size_t
    // size_t == unsigned int
        size_t i = 0;
        size_t j = 0;

        //共进行num-1趟
        for (i = 0; i < num; i++)
        {
                //每一趟
                for (j = 0; j < num - 1 - i; j++)
                {
                        //比较
                        //传地址   
                        //相邻两个元素比较   width:宽度,每个元素所占字节
                        //排成升序
                        if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
                        {
                                //交换两数
                                Swap( (char*)base + j * width, (char*)base + (j + 1) * width, width );
                        }
                }
        }
}
当然 ,交换也可以使用库函数memcpy



dest:目标空间

src:要拷贝到目标空间的字符 -因为不作修改,所以可以用const修饰

count:字节数

char tmp [30];    //防止结构体类型之类的类型    临时空间
memcpy(tmp, (char*)base + j * size, size);
memcpy( (char*)base + j * size,  (char*)base + (j + 1) * size, size);
memcpy( (char*)base + (j + 1) * size, tmp, size);

————————————————
武汉兰树网络科技有限公司
www.ls-idc.com
QQ:775260000
TG:@lsidc
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

论坛客服/商务合作/投诉举报:2171544 (QQ)
落伍者创建于2001/03/14,本站内容均为会员发表,并不代表落伍立场!
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论!
落伍官方微信:2030286 邮箱:(djfsys@gmail.com|tech@im286.com)
© 2001-2014

浙公网安备 33060302000191号

浙ICP备11034705号 BBS专项电子公告通信管[2010]226号

  落伍法律顾问: ITlaw-庄毅雄

手机版|找回帐号|不能发帖?|Archiver|落伍者

GMT+8, 2024-11-26 14:50 , Processed in 0.058199 second(s), 31 queries , Gzip On.

返回顶部