在c语言中我们经常会用到排序,排序的方法也有很多,对于一部分初学者来说最常用到的就是冒泡排序和选择排序,但是有一个函数可以帮助我们快速的排序
qsort函数,本文章适合学习过指针,或者对指针有了解的同学研讨
通过本文章的学习,可以自己编写自己的qsort函数
我们先来了解一下qsort函数
qsort函数是c语言编译器自带的排序函数,使用时对标准库函数stdlib.h进行声明
qsort函数的原型是
void qsort(void*base,size_t num,size_t width,int(*cmp)(const void*,const void*))
void qsort(void* base,表示需要排序内容的地址注意void*是没有办法直接解引用的
size_t num,需要排序内容的个数
size_t width,需要排序数组中的数据占据字节大小
int(*cmp)(const void*,const void*)用来比较待排序数据中的2个元素 判断比较方式 比较方式需要使用者自己写一个函数来判断
);
此处的int(*cmp)(const void*,const void*)表示一个函数指针
明白了qsort函数的组成之后我们来应用一下qsort函数
#include
#include
int cmp_int(const void*e1,const void *e2)
{
return *(int*)e2-*(int*)e1;
}
int main()
{
int arr[]={0,1,2,3,4,5,6,7,8,9};
int i;
qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),cmp_int);
for(i=0;i<10;i++)
printf("%d",arr[i]);
return 0;
}
在 cmp_int函数中返回类型要强制转化为(int*)型这是为什么呢?
我们在上文中提到过void*是没有办法直接解引用的,因为我们是对一个数组中的数字进行排序,所以我们直接转化为int*型
同样值得我们关注的点是return中返回值决定了我们是对一个数组中数据进行从大到小排序,还是从小到大排序。
如果返回值<0那么e1所指向的元素会被排在e2所指向的元素的左面
如果返回值=0那么e1,e2所指向的元素排序不确定
如果返回值>0,那么e1所指向的元素会被排在e2所指向的元素右面
随着我们日后的学习我们会发现qsort函数除了能对数字排序还可以对字符串,结构体关键字,结构体字符串等排序
接下来我们对自己的qsort函数进行编写
首先我们在此处应该明白最基本的排序就是冒泡排序,为了方便大家更好的理解,所以我们在自己编写时,对于排序的部分也使用冒泡排序
接下来我们对自己的qsort函数进行分析
qsort函数由三部分组成1.函数主体我们命名为bubble_sort2.交换函数swap3.比较函数cmp
1.bubble_sort(void*case,int sz,int width,int(*cmp)(const void*e1,const void*e2);
bubble_sort(void*case同qsort函数一样我们此处的void*case是一个无定型指针要求输入我们要排序内容的地址
int sz表示我们要排序内容的个数
int width表示我们要排序的内容占据的字节大小
int(*cmp)(const void*e1,const void*e2)此处我们插入一个函数指针指向我们定义的比较函数
特别注意的是因为我们此处只是进行计算,所以加const保证指针指向的值不会改变
);
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i, j;
//因为我们排序的方式仍然为冒泡排序所以我们采用冒泡排序的方式
for (i = 0;i < sz - 1;i++)
{
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);
}
}
}
}
我们发现if中的内容比较奇怪
if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
接下来我们分析这个if:因为我们不知道用户使用我们的这个函数对何种的数据进行排序,所以我们直接把需要比较的内容强制类型转化为(char*)变成单个的字节,再通过+j*width来确定我们每个元素的地址,同qsort函数一个原理如果我们比较函数的返回值时正数,那么我们对量数据进行交换。
比如:我们要排序int型的数据,我们知道int型数据占据4个字节,所以此时的width为4,当我们把整个数据都转化为(char*)的单个字节时我们+或者- 4或者4的倍数就表示+或者-一个整型数据的地址。
因此我们就通过这样的方式来进行两个元素之间地址的输入
2.swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
根据上面的分析我们可以理解此处swap函数也通过同样的手法来进行地址的输入
在交换时因为我们无法判断到底用户使用什么样的数据进行交换,所以我们干脆不进行判断,而是选择更为简单粗暴的把字节交换,所以我们的swap函数就需要知道我们每次交换的次数即一个数据所占据的字节大小
void swap(char* buf1, char* buf2, int width)
{
int i;
for (i = 0;i < width;i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
当我们对两个数据进行交换之后buf1和buf2分别++指向下面两个需要交换的数据
3. cmp((char*)base+j*width,(char*)base+(j+1)*width)
接下来我们对比较函数进行分析,通过上面的讲解我们已经明白(char*)的强制类型转换可以帮助我们输入需要比较两个元素的地址
int cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
此处我们的cmp函数和qsort函数中的cmp函数作用相同,此处不再过多赘述。
通过上面对三个主要部分的分析我们已经明白了我们自己的qsort函数的组成和每部分的内容
#include
int cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void swap(char* buf1, char* buf2, int width)
{
int i;
for (i = 0;i < width;i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i, j;
for (i = 0;i < sz - 1;i++)
{
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);
}
}
}
}
int main()
{
int arr[] = {10,5,6,9,2,3,1,4};
bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), cmp);
int i;
for (i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
printf("%d", arr[i]);
return 0;
}
如果自己能写一个自己的qsort函数是不是很有成就感呢?好了,本篇文章到此结束
谢谢大家,如果大家有更好的想法欢迎大家留言,或者在评论区相互探讨!