利用qsort函数排序多种类型数据以及模仿qsort函数的实现


一、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函数的理解上有帮助,请给个赞支持下谢谢!
  • 20
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zjruiiiiii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值