文章目录
一、qsort函数介绍
我们在此之前应该大概了解过多种排序的方法,例如最常见的冒泡排序,选择排序,归并排序等等,而利用qsort函数实现排序简称快速排序。它的计算速度非常快,所以系统也在库里实现这个算法,便于我们的使用。它是ANSI C标准中提供的,其声明在stdlib.h文件中,是根据二分法写的,其时间复杂度为n*log(n)。
功能:qsort()函数是 C 语言库中实现的快速排序。
头文件: stdlib.h
函数原型:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
可见,当我们要使用qsort函数进行快速排序时,必须要传4个参数给qsort函数接收,单单看函数原型可能大部分读者都不是特别理解,下面就由我来引导大家如何准确使用qsort函数。
二、qsort函数传参内容分析
1.第一个参数是需要排序的数组的基地址,而我们知道,当我们在主函数中传参时是数组名,则传到自定义函数体中则是该数组的首元素地址,而我们观察是void*类型,因此我们传过去的数组类型不限(即任意数组类型)。
2.第二个参数是待排序的数量,即你要将数组中要排序的数量传进qsort函数中,而类型是size_t,表示的是无符号整型,传一个正数到qsort函数即可。
3.第三个是单个数组元素的大小,即字节数,数组元素的大小用sizeof操作符进行计算,格式是sizeof(数组名[0])。
4.第四个参数是一个指向函数的指针,其作用是规定排序的规则,即按照什么样的方式进行排序。这里光看概念不容易看懂,最好用例子来进行实现。
三、总体思路与注意事项
先创建整型数组,初始化值,将调用qsort函数所需的四个参数依次填入,要按规定的顺序进行传参。由于调用qsort函数时要调用另一个函数返回所需的值进行升序或降序排序(以下举例均为mycmp函数)。在mycmp函数中固定要用两个参数接收 。const的作用只是让程序员不能改变该地址,不必想的过于复杂,其实const是否加上不影响结果,但为了严谨最好加上const。
在mycmp函数中,我参考了许多篇文章都写得太过冗余与复杂,并且代码量比较大。特别是*a 与 *b 的比较之后返回给qsort参数的值写的太乱,在这里我对此做个总结。以下的代码 *a 与 *b的比较, *a是一定大于 *b的,如果我们想让排序为升序,则返回1,降序则返回-1,只需将 *a 与 *b的位置调换即可。这里我们可以在所有利用三目操作符来实现避免if语句的繁琐。
四、对多种数据类型进行排序
1.对int型数组排序
#include <stdio.h>
#include <stdlib.h>
int mycmp(const void* p1, const void* p2)//固定格式
{
const int* a = (const int*)p1;//将p1、p2强制类型转换
const int* b = (const int*)p2;
int value = 0;
value = * a > * b ? 1 : -1;
return value;//升序返回1,降序返回-1
}
int main()
{
int num[3] = { 2,5,3 };
qsort(num, 3, sizeof(num[0]), mycmp);
for (int i = 0; i < 3; i++)
{
printf("%d ", num[i]);
}
return 0;
}
代码实现结果如图:
2.对char型数组排序
#include <stdio.h>
#include <stdlib.h>
int mycmp(const void* p1, const void* p2)
{
const char* a = (const char*)p1;
const char* b = (const char*)p2;
int value = 0;
value = * a > * b ? 1 : -1;
return value;
}
int main()
{
char num[] = {'a','c','b'};
qsort(num, 3, sizeof(num[0]), mycmp);
for (int i = 0; i < 3; i++)
{
printf("%c ", num[i]);
}
return 0;
}
代码实现结果如图:
3.对double型数组排序
补充:在double中如果是两个很接近的数则可能返回一个很小的小数(大于-1,小于1),而cmp的返回值是int型,因此会将这个小数返回0,系统认为是相等,失去了本来存在的大小关系,因此此处必须要使用三目操作符进行判断。
#include <stdio.h>
#include <stdlib.h>
int mycmp(const void* p1, const void* p2)
{
const double* a = (const double*)p1;
const double* b = (const double*)p2;
int value = 0;
value = * a > * b ? 1 : -1;
return value;
}
int main()
{
double num[] = { 3.0,5.0,7.0 };
qsort(num, 3, sizeof(num[0]), mycmp);
for (int i = 0; i < 3; i++)
{
printf("%lf ", num[i]);
}
return 0;
}
代码实现结果如图:
4.对字符串型数组排序
#include <stdio.h>
#include <stdlib.h>
int mycmp(const void* p1, const void* p2)
{
const char* a = (const char*)p1;
const char* b = (const char*)p2;
int value = 0;
value = * a > * b ? 1 : -1;
return value;
}
int main()
{
char num[100][10] = {"ab","ef","cd"};
qsort(num, 3, sizeof(num[0]), mycmp);
for (int i = 0; i < 3; i++)
{
printf("%s ", num[i]);
}
return 0;
}
代码实现结果如图:
5.对结构体排序
#include <stdio.h>
#include <stdlib.h>
struct Stu
{
char name[20];
int age;
};
int mycmp_struct(const void* p1, const void* p2)//固定格式
{
return ((struct Stu*)p1)->age-((struct Stu*)p2)->age;//升序
}
int main()
{
struct Stu s[2] = { {"zhangsan",15},{"lisi",13} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), mycmp_struct);
return 0;
}
经过调试最后发现,zhangsan和lisi的年龄调换了位置(即升序),如果想要降序,则在 return ((struct Stu*)p1)->age-((struct Stu*)p2)->age; 处调换p1与p2的位置即可。
如果想让结构体中的字符串进行升序排序
#include <stdio.h>
#include <stdlib.h>
struct Stu
{
char name[20];
int age;
};
int mycmp_struct(const void* p1, const void* p2)//固定格式
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int main()
{
struct Stu s[2] = { {"zhangsan",15},{"lisi",13} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), mycmp_struct);
return 0;
}
模仿qsort函数
前面我们已经对qsort函数有了一定的介绍。模仿qsort函数我们要求一样能够解决不同类型的数据排序。这里我们直接用int数组进行升序排列,先看代码。
#include <stdio.h>
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++;
}
}
int cmp_int(const void* e1, const void* e2)
{
return ((*(int*)e1) - *(int*)e2);
}
void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void*,const void*))
{
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
size_t j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width))
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
int main()
{
int arr[] = { 3,2,1 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
在此我用my_qsort函数来模仿库函数的qsort,我们传的参数跟库函数中的qsort函数是一致的,只是在my_qsort函数内部有所不同。
相信读者阅读到此篇文章的程度,对冒泡排序一定非常熟悉。都是先考虑一个数一共有几趟需要排序,而一趟排序当中又要有几对数要进行比较,如果前者比后者大,则大的数向后移动,直到没有数比较为此,以此类推。
冒泡排序的代码如下:
void bubble_sort(int arr[], int sz)
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序
int j = 0;
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;
}
}
}
}
模仿qsort函数最巧妙的地方就在于if语句的判断内容以及Swap函数是如何交换的。在if语句中,我们用cmp的返回值来进行判断,如果升序则返回大于0的数,降序则返回小于0的数。我们在对两个值进行比较时,无法判断这两个数值的类型是什么,这里我们将base指针强制类型转换为char来进行比较。每个不同的数据类型的指针决定的是+1移动的是多少个字节。char每次加1就移动1个字节。而width又是每个值的字节数,因此我们用(char*)base+jwidth与(char)base+(j+1)*width来实现两个值的比较(无论是结构体的字符串类型还是整型都能够比较)(j+1是指前后两位的比较,例如3,5,6。是3和5的比较)。
进入判断部分后要进行交换。交换时我们要把两个数值的地址传进Swap函数中,注意这里还要把它们的宽度(一个值的字节数)传进去,并用两个char型指针与width接收。Swap函数内部利用循环并且用char类型来每一个字节进行比较,直到所有字节交换完毕后,即两个值已经完全进行交换,退出循环。
如果对本文有什么补充与不足欢迎前来讨论,如果本文对您在qsort函数的理解上有帮助,请给个赞支持下谢谢!