qsort函数的介绍和模拟实现

在前面的文章中,我已经提到了一种排序的方法,也就是冒泡排序,但是细心的小伙伴不难发现,前面的冒泡排序的程序只能排序整形,然而在实际应用中,排序不仅要排序整形和浮点型,更要排序字符,甚至是结构体,今天,我们就要讲一讲能够实现该要求的一个库函数—qsort函数。





qsort函数参数的讲解

请添加图片描述
通过msdn查询可知,qsort函数有四个参数,有的小伙伴看见如此多的参数就开始害怕了,别急,跟着我的节奏,多少个参数都不怕。
请添加图片描述
在msdn的下面,有对四个参数的讲解。

base ------ 指向目标数组的首元素的地址
num ------ 数组元素个数
width ------ 每个元素的大小(字节)
compare ------ 函数指针 , elem1与elem2是两个需要比较的元素

在qsort函数需要的指针中, 除函数指针外,其他指针都是用void*,如:base,elem1,elem2。这里需要引入一个小知识,void*可以接受任何类型的指针,这就解决了我们在排序各种各样的数据类型时,传参的类型不统一的问题。

至于函数指针不用void*的原因,与后面程序的设计有关,先不用着急。

在msdn下面,还有对qsort函数参数的简单描述。

请添加图片描述
这里,我直接翻译:

qsort函数实现了一个快速排序算法,对num个元素的数组(每个元素都是width字节)进行排序。参数base是指向要排序的数组的基的指针。qsort用排序后的元素覆盖这个数组。参数compare是指向用户提供的例程的指针,该例程比较两个数组元素并返回指定它们之间关系的值。qsort在排序过程中调用一次或多次比较例程,每次调用时传递指向两个数组元素的指针

对于qsort函数的参数已经做了简单介绍,在后面有几个qsort函数排序例子,认真理解,对于qsort函数的参数使用会更加的得心应手。



函数指针的返回类型

请添加图片描述
函数指针是在设计程序中,根据不同的类型数据来设计的,但返回类型规定相同。如果,elem1小于elem2,返回小于0;如果elem1大于elem2,返回大于0;如果elem1等于elem2,返回等于0。

返回值
若为0,两者不进行交换
大于0,两者进行交换
小于0,两者不进行交换

至于如何设计该函数指针,等到后面讲到qsort函数的例子时,会详细介绍,不要着急。



qsort函数排列整形的例子

#include<stdio.h>
#include<stdlib.h>
void print(int arr[],int sz)               //打印的函数
{
    int i = 0;
	for(i=0;i<sz;i++)
	{
	    printf("%d ",arr[i]);
	}
	printf("\n");
}
int com_int(const void* e1,const void* e2)            //函数指针(注意参数void*)
{
	return *(int*)e1 - *(int*)e2;
}
int main()
{
	int arr[] = {3,1,2,4,6,8,5,7,10,9};    //创建整形数组
	int sz = sizeof(arr)/sizeof(arr[0]);   //计算元素个数
	print(arr,sz);                         //打印排序前的数组
	qsort(arr,sz,sizeof(arr[0]),com_int);  //注意传参的要求
	print(arr,sz);                         //打印排序后的数组
    return 0;
}

运行结果如下:

请添加图片描述

如果要完成降序的话,将e1和e2交换位置就可以了。因为是否交换e1和e2是要看函数指针的返回值是否大于0。

现在我来解决前面遗留的问题,第一个,qsort函数的参数问题。在这个例子中,我分别往qsort函数传首元素地址,元素个数,单个元素大小(字节),函数指针。并且在函数指针的设计方面,严格按照qsort函数的要求,采用void*。在以后排序其他类型的数据时,我们需要改动的是qsort函数的参数的函数指针,我们需要设计不同的函数指针来排序我们的数据。

第二个,在这个排序整形的函数指针中,(void*)需要先转换(int*),然后直接返回两者的差值。如果e1大于e2,相减之后,自然大于0;如果e1小于e2,相减之后,自然小于0;如果e1等于e2,相减之后,自然等于0。在这样巧妙的设计下,也符合了qsort函数的函数指针返回值的要求。



qsort函数排列结构体的例子

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu                       //创建结构体
{
    char name[20];
	int age;
};
int com_stu_by_name(const void* e1,const void* e2)    //函数指针(采用名字排序)
{
	return strcmp(((struct stu*)e1)->name ,((struct stu*)e2)->name);
}
int com_stu_by_age(const void* e1,const void* e2)
{
    return (((struct stu*)e1)->age - ((struct stu*)e2)->age);
}
int main()
{
	struct stu s[3] = {{"zhangsan",90},{"lisi",80},{"wangwei",60}};
	int sz = sizeof(s)/sizeof(s[0]);                                      //计算结构体的大小
	qsort(s,sz,sizeof(s[0]),com_stu_by_name);                           //采用名字排序  
	qsort(s,sz,sizeof(s[0]),com_stu_by_age);                            //采用年龄排序
    return 0;
}

在该例子中,创建结构体和qsort函数的传参可能没有太大大难度。难度较大的便是函数指针的设计问题,我们得记住,对于结构体的排序,要考虑是根据结构体的哪一方面进行排序。

如上面所设计的一样,我分别从名字和年龄方面进行设计函数指针,并进行排序。我们先看设计名字的函数指针,名字以字符形式进行存储,而比较两个字符的大小,我想到了strcmp函数。
请添加图片描述
从msdn查询strcmp函数的返回类型可以发现,strcmp的返回类型与qsort函数的函数指针返回类型要求是一致的,所以直接return就行。

我们再看设计年龄函数指针的函数指针,为了迎合qsort函数的函数指针的返回类型,我们依然直接return相减的结果。

我们直接通过调试的方法观察排序的结果

排序前:
请添加图片描述
每个元素的位置与初始化时相同。

采用名字排序后:
请添加图片描述
每个元素开始按照名字首字母进行排序。

采用年龄进行排序后:
请添加图片描述
每个元素按照年龄大小进行排序



qsort函数排序浮点型的例子

#include<stdio.h>
#include<stdlib.h>
int print(float arr[],int sz)          //打印的函数 
{
    int i = 0;
	for(i=0;i<sz;i++)
	{
	    printf("%.1f ",arr[i]);
	}
	printf("\n");
}
int com_float(const void* e1,const void* e2)
{
    if(*(float*)e1 > *(float*)e2)
		return 1;
	else if(*(float*)e1 < *(float*)e2)
		return -1;
	else
		return 0;
}
int main()
{
	float arr[] = {3.2 , 1.1 , 5.6 , 4.8 , 8.9 , 6.5 , 7.8 , 9.0 , 9.6, 10.0};    //创建浮点型数组
    int sz = sizeof(arr)/sizeof(arr[0]);
	print(arr,sz);                                                                //打印排序前数组元素
	qsort(arr,sz,sizeof(arr[0]),com_float);
	print(arr,sz);                                                                //打印排序后数组元素
	return 0;
}

运行结果如下:
请添加图片描述
在前面的文章中,我已经提到浮点型存在精度缺少的问题,所以在这里的qsort函数的函数指针设计中,不再通过两者相减的方式,而是通过比大小的方式来确定返回值。

如果小伙伴有兴趣的话,可以参照排序整形的函数指针设计来设计排序浮点型的函数指针,就可以发现,排序结果不能达到完全升序和完全降序。总是存在几个数据排序错误,这是因为在浮点型的精度缺少问题,可参考前面的文章《浮点数的精度缺少》。

采用冒泡排序思想实现qsort函数

qsort函数的底层原理是快速排序,但是我们还是可以通过冒泡排序的思想来实现qsort函数。在《冒泡排序(详细)》的文章中,我已经对冒泡排序的原理进行详细的介绍,但是有一点不足的是,该冒泡排序的程序只能排序整形,接下来,我将进行优化,让这个冒泡排序能够排序各种类型的数据。

在模拟实现时,我们需要深层的了解qsort函数。

在排序不同的数据类型时,最先解决的就是存储的字节数不同,char有1个字节,int有4个字节,结构体有20个字节。不同字节数导致什么问题呢?难以找到下个元素。

如:char跳过一个字节找到下一个元素
如:int跳过4个字节找到下一个元素

qsort函数的base参数和width参数起到了作用,前面就有过介绍,base参数是被排序数组的第一个元素,width是元素的大小,那我们不是可以如此设计:

base ------ 第一个元素
base + 1个width ------- 第二个元素
base + 2个width ------- 第三个元素

解决了不同类型数据的字节大小不同的问题,使得程序不用为某一个种类型的数据进行设计,大大提高了代码的效率。

在qsort函数的内部,还有一个让我惊叹的设计,那就是交换函数。同样是要解决这个不同类型数据字节的不相同,将数据强制类型转换为char*类型,通过一次改变一个字节的方法来进行交换。次数与width参数相等。

如:(int*)类型的数据,强制类型转换为(char*),修改width(4)个字节。

介绍到这里,我们对qsort函数的有了更深的了解,接下来直接采用冒泡排序的思想来实现。

void swap(char* buf1,char* buf2,int width)     //交换函数
{
    int i = 0;
	for(i=0;i<width;i++)              //交换width个字节
	{
	    char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;                       
		buf2++;
	}
}
void bubble_sort(void* base,int num,int width,int compare(const void* e1,const void* e2))
{
    int i = 0;
	int j = 0;
	for(i=0;i<num-1;i++)
	{
	    for(j=0;j<num-1-i;j++)
		{
		    if(compare((char*)base+j*width,(char*)base+(j+1)*width)>0)        //通过base参数和width参数找到下一个元素
			{
			    swap((char*)base+j*width,(char*)base+(j+1)*width,width);      //在compare的函数指针比较下,大于零就进行交换
			}
		}
	}
}
int main()
{
    return 0;
}

这就是qsort函数模拟实现的内部了,如果要排序哪种数据,依然通过写函数指针,传参来排序。

我对代码里面的buf1和buf2再画图解释吧。
请添加图片描述
buf1和buf2交换后,再进行加加,知道遍历了width个字节,两个元素的内容就交换完成了。



采用冒泡排序思想实现qsort函数(排序整形)

#include<stdio.h>
int com_int(const void* e1,const void* e2)      //函数指针
{
    return *(int*)e1 - *(int*)e2;
}
void swap(char* buf1,char* buf2,int width)     //交换函数
{
    int i = 0;
	for(i=0;i<width;i++)              //交换width个字节
	{
	    char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;                       
		buf2++;
	}
}
void bubble_sort(void* base,int num,int width,int compare(const void* e1,const void* e2))
{
    int i = 0;
	int j = 0;
	for(i=0;i<num-1;i++)
	{
	    for(j=0;j<num-1-i;j++)
		{
		    if(compare((char*)base+j*width,(char*)base+(j+1)*width)>0)        //通过base参数和width参数找到下一个元素
			{
			    swap((char*)base+j*width,(char*)base+(j+1)*width,width);      //在compare的函数指针比较下,大于零就进行交换
			}
		}
	}
}
void print(int arr[],int sz)                                                  //打印的函数                                   
{                                         
	int i = 0;
	for(i=0;i<sz;i++)
	{
	    printf("%d ",arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = {3,1,2,4,6,8,5,7,10,9};
	int sz = sizeof(arr)/sizeof(arr[0]);
	print(arr,sz);                                      //打印排序前
	bubble_sort(arr,sz,sizeof(arr[0]),com_int);         //bubble_sort函数传参
	print(arr,sz);                                      //打印排序后
    return 0;
}

运行结果如下:
请添加图片描述



采用冒泡排序思想实现qsort函数(排序结构体)

#include<stdio.h>
#include<string.h>
struct stu                                             //创建结构体
{
    char name[20];
	int age;
};
int com_stu_by_name(const void* e1,const void* e2)                    //采用名字排序的函数指针
{
	return strcmp(((struct stu*)e1)->name,((struct stu*)e2)->name);
}
int com_stu_by_age(const void* e1,const void* e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
void swap(char* buf1,char* buf2,int width)     //交换函数
{
    int i = 0;
	for(i=0;i<width;i++)              //交换width个字节
	{
	    char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;                       
		buf2++;
	}
}
void bubble_sort(void* base,int num,int width,int compare(const void* e1,const void* e2))
{
    int i = 0;
	int j = 0;
	for(i=0;i<num-1;i++)
	{
	    for(j=0;j<num-1-i;j++)
		{
		    if(compare((char*)base+j*width,(char*)base+(j+1)*width)>0)        //通过base参数和width参数找到下一个元素
			{
			    swap((char*)base+j*width,(char*)base+(j+1)*width,width);      //在compare的函数指针比较下,大于零就进行交换
			}
		}
	}
}
int main()
{
	struct stu s[] = {{"zhangsan",90},{"lisi",80},{"wangwei",60}};
	int sz = sizeof(s)/sizeof(s[0]);
	bubble_sort(s,sz,sizeof(s[0]),com_stu_by_name);                           //采用名字进行排序
	bubble_sort(s,sz,sizeof(s[0]),com_stu_by_age);                            //采用年龄进行排序
    return 0;
}

调试观察运行结果:

排序前:请添加图片描述
按照初始化的位置排序

采用名字排序后:
请添加图片描述
按照名字首字母顺序进行排序

采用年龄排序后:
请添加图片描述
按照年龄大小顺序进行排序

采用冒泡排序思想实现qsort函数(排序浮点型)

#include<stdio.h>
void print(float arr[],int sz)
{
    int i = 0;
	for(i=0;i<sz;i++)
	{
	    printf("%.1f ",arr[i]);
	}
	printf("\n");
}
int com_float(const void* e1,const void* e2)
{
    if(*(float*)e1 > *(float*)e2)
		return 1;
	else if(*(float*)e1 < *(float*)e2)
		return -1;
	else
		return 0;
}
void swap(char* buf1,char* buf2,int width)     //交换函数
{
    int i = 0;
	for(i=0;i<width;i++)              //交换width个字节
	{
	    char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;                       
		buf2++;
	}
}
void bubble_sort(void* base,int num,int width,int compare(const void* e1,const void* e2))
{
    int i = 0;
	int j = 0;
	for(i=0;i<num-1;i++)
	{
	    for(j=0;j<num-1-i;j++)
		{
		    if(compare((char*)base+j*width,(char*)base+(j+1)*width)>0)        //通过base参数和width参数找到下一个元素
			{
			    swap((char*)base+j*width,(char*)base+(j+1)*width,width);      //在compare的函数指针比较下,大于零就进行交换
			}
		}
	}
}
int main()
{
	float arr[] = {3.2 , 1.1 , 5.6 , 4.8 , 8.9 , 6.5 , 7.8 , 9.0 , 9.6, 10.0};
	int sz = sizeof(arr)/sizeof(arr[0]);
	print(arr,sz);                                                              //打印排序前
	bubble_sort(arr,sz,sizeof(arr[0]),com_float);                               //bbubble_sort传参
	print(arr,sz);                                                              //打印排序后
    return 0;
}

运行结果如下:
请添加图片描述
在这篇文章详细的对qsort函数进行介绍并对qsort函数进行模拟实现,知识点较多,值得收藏反复学习,期待关注,下次更精彩。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值