qsort各种用法大全以及实现
Posted 梦乡回雪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了qsort各种用法大全以及实现相关的知识,希望对你有一定的参考价值。
详解qsort各种用法以及实现
文章目录
前言
- 在文章开头,我们先来说一下qsort的作用。在正常我们写完一个冒泡排序的时候比如void Bubble_sort(int arr[],int sz);那么我们在用这个函数的时候就只能去给一个整型数组大小排序,当我们想要去排序浮点型,字符型的时候该函数就不适用了,而qsort函数就能解决这个问题,只要你规定了排序方法,你就可以去排序字符,整型,浮点,结构体,做到万物皆可排。
一、qsort定义
首先我们打开MSDN,搜索一下qsort
- 作用:Performs a quick sort.(执行快排)
- 头文件:<stdlib.h>
- 参数和返回类型:
void qsort(
void *base, //接收所要排序的目标数组名
size_t num, //接收排序数组的个数(以元素为单位)
size_t width, //每一个元素大小(以字节为单位)
int (__cdecl *compare )(const void *elem1, const void *elem2 ) //自定义的比较方法
);
-
这里要注意qsort函数里的void* , 这里一旦写成其他类型都会只能对一种数据类型排序,而空类型void*正好可以接收其他任何类型,从而当做参数!
-
自定义比较函数
这里MSDN上给出了一些关于该函数的说明,具体大家可以自行查看 -
参数类型:
int compare( const void * elem1,const void * elem2 ); -
返回值:
并且注意按上边这种返回值去写比较函数的话,最终是得到的是升序排列。
那么返回值相反就会得到降序排列。
二、qsort排序应用
1、整型排序
- 首先我们给出一个乱序整型数组,
int arr[] = { 1,3,4,8,2,6,7,5,9,10 };
- 接着按要求去写qsort函数
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
- 实现自定义cmp_int函数
那么这里也就是第一个参数小于第二个参数的话返回值 < 0;
第一个参数大于第二个参数的话返回值 > 0;
第一个参数等于第二个参数的话返回值 = 0;
- 具体实现:
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
对传进来的e1和e2用void* 来接收,并且考虑到使用的时候是对整型数组排序,因此强转成int * ,
用e1-e2,e1 > e2,返回值大于0,e1 < e2,返回值小于0,e1 = e2,返回值等于0,满足要求。
完整代码:
#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 1,3,4,8,2,6,7,5,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果:
- 那么如果你想要实现降序只需修改一下自定义比较函数的返回值(e2-e1)即可。
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e1;
}
2、字符型排序
- 我们知道字符是以ASC码中对应的数字表示的,因此在写比较函数时直接相减即可。
#include<stdio.h>
#include<stdlib.h>
int cmp_char(const void* e1, const void* e2)
{
return *(char*)e1 - *(char*)e2;
}
int main()
{
char a[] = { 'a','c','e','d','b' };
int sz = sizeof(a) / sizeof(a[0]);
qsort(a, sz, sizeof(a[0]), cmp_char);
for (int i = 0; i < sz; i++)
{
printf("%c ", a[i]);
}
return 0;
}
3、浮点型排序
- 这里只需要注意浮点型的数据储存在计算机中是有误差的,可能在你的电脑是1,那么其他电脑上就是2了。因此浮点数相等的比较不能直接相减,只能用fabs(a-b)<1e-20之类的这样来判断,所以这里只返回了1和-1。
#include<stdio.h>
#include<stdlib.h>
int cmp_double(const void* e1, const void* e2)
{
return ((*(double*)e1 - *(double*)e2) > 0)? 1:-1;
}
int main()
{
double a[] = { 1.2,56.4,0.56,456.89,32.4 };
int sz = sizeof(a) / sizeof(a[0]);
qsort(a, sz, sizeof(a[0]), cmp_double);
for (int i = 0; i < sz; i++)
{
printf("%.2f ", a[i]);
}
return 0;
}
4、字符串排序
#include<stdio.h>
#include<stdlib.h>
int cmp_string(const void* e1, const void* e2)
{
return strcmp((char*)e1, (char*)e2);
}
int main()
{
char a[5][6] = { "h","he","hel","hell","hello" };
int sz = sizeof(a) / sizeof(a[0]);
qsort(a, sz, sizeof(a[0]), cmp_string);
for (int i = 0; i < sz; i++)
{
printf("%s ", a[i]);
}
return 0;
}
- 值得注意的就是strcmp函数里边强转类型为char*后正好是 每一串字符的首地址,不用在解引用了。
5、结构体排序
struct student
{
char name[20];
int age;
};
这里我们定义了一个结构体,那么接下来按照名字和年龄两种排序情况说明
1. 按照name排序
#include<stdio.h>
#include<stdlib.h>
struct student
{
char name[20];
int age;
};
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((student*)e1)->name, ((student*)e2)->name);
}
void test()
{
student s[3] = { {"z",15},{"l",17},{"w",20} };
qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_name);
for (int i = 0; i < 3; i++)
{
printf("%s ", s[i].name);
}
}
int main()
{
test();
return 0;
}
字典序结果:
2. 按照年龄排序:
#include<stdio.h>
#include<stdlib.h>
struct student
{
char name[20];
int age;
};
int cmp_age(const void* e1, const void* e2)
{
return ((student*)e1)->age - ((student*)e2)->age;
}
void test1()
{
student s[3] = { {"张三",15},{"李四",17},{"王伟",20} };
Bubble_sort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_age);
for (int i = 0; i < 3; i++)
{
printf("%s ", s[i].name);
}
}
int main()
{
test1();
return 0;
}
三、实现qsort
这里我们采用冒泡排序来实现qsort函数:
void Bubble_sort(void* base, size_t num, size_t width, int(* compare)(const void* elem1, const void* elem2))
{
for (size_t i = 0; i < num; i++)//冒泡排序的趟数
{
for (size_t j = 0; j < num - 1 - i;j++)//每一趟
{
//判断交换
}
}
}
- 那么框架写完了,接下来就是判断大小。规则在compare函数中用户定义,那么怎么传参呢?
- 由于比较函数要求的是要每个用来比较元素的地址,那么对于第n个元素我们只需要用base + j * width,即可找到第j个元素的地址。
- 但是要注意这里我们要(char*)base + j * width来强转一下,我们知道char类型1个字节,int 4个字节,各种数据类型中最小的单位都是1个字节,其他的都是其的n倍,因此(char*)强转可以更好的进行以字节为单位的数据交换!!!
void Bubble_sort(void* base, size_t num, size_t width, int(* compare)(const void* elem1, const void* elem2))
{
for (size_t i = 0; i < num; i++)//冒泡排序的趟数
{
for (size_t j = 0; j < num - 1 - i;j++)//每一趟
{
if (compare((char*)base+j*width,(char*)base+(j+1)*width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
- 接下来就是swap函数的实现了
对于一个整型数组:
其实际存储如图:(大端存储)
- 每一个int型元素都是有四个字节,需要把第一个元素和第二个元素在内存中的数据完全交换,而这里正好验证上边,以字节为单位能够方便很多,宽度是width(单位是字节),只需要把每一个字节的内容对应交换即可!!
第一种写法:
完整代码:
void Swap(char* buffer1, char* buffer2,int width)
{
for (int i = 0; i < width; i++)
{
char t = *buffer1;
*buffer1 = *buffer2;
*buffer2 = t;
buffer1++;
buffer2++;
}
}
void Bubble_sort(void* base, size_t num, size_t width, int(* compare)(const void* elem1, const void* elem2))
{
for (size_t i = 0; i < num; i++)//冒泡排序的趟数
{
for (size_t j = 0; j < num - 1 - i;j++)//每一趟
{
if (compare((char*)base+j*width,(char*)base+(j+1)*width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
第二种写法:
void myqsort(void * base, size_t nitems, size_t size, int(*compar)(const void *, const void *))
{
int i, j;
char * st = (char *)base; //void *不方便加减,用char *加减轻松且字节跳转为1,方便控制。
char tmp[16]; //考虑到long double类型,临时空间做成16字节比较保险
for (i = 0; i < nitems - 1; i++)
{
for (j = 0; j < nitems - 1 - i; j++) //冒泡常用循环头
{
if (compar(st + j * size, st + (j + 1) * size)) //比较的时候跳转注意乘size
{
memcpy(tmp, st + j * size, size); //交换操作用memcpy完成就不会出问题。
memcpy(st + j * size, st + (j + 1) * size, size);
memcpy(st + (j + 1) * size, tmp, size);
}
}
}
}
qsort库函数的用法