C语言——快速排序qsort()库函数的运用以及模拟实现

目录

⚽️🏀库函数qsort的使用方法🏀⚽️

🌈qsort()库函数使用例子代码🌈

🐠 🐟  qsort()库函数的模拟实现🐠 🐟 

💦冒泡排序

冒泡排序的代码

 ⚡️冒泡排序二代⚡️

🍀swp函数🍀

🌕完整模拟代码

🥥运行截图


⚽️🏀库函数qsort的使用方法🏀⚽️

🥬🥬我们可以看到qsort()库函数有4个参数:

分别是:1.void*base

  指向排序的数组的第一个对象的指针。为什么是用void* 指针类型来接受呢?因为qsort()设计的时候可以排序多种类型的。但是这个函数的作者并不知道我们使用者需要排序什么类型的数据,传参的是什么类型的指针,void*类型的指针是比较宽容的,可以接收多种类型的指针。

              2.size_t num

       待排序元素的个数

              3.size_t size

每一个元素所占内存空间大小,单位是(字节)

4.int (*cmp )(const void*p1,const void*p2)

这是一个函数指针,指向我们需要调用的函数。这个调用的函数是用来设置比较方法的。为什么要设置比较的方法呢?因为我们每一种类型的比较方法是不一样的。整形(int)类型

我们知道要用 > 、<、 =、来比较,字符串就不可以用我们用 > 、<、 =、来比较了,strcmp()库函数来比较大小;还有一些其他的类型,所以我们要自己设置一个比较函数传参给qsort函数(回调函数的使用)。

p1和p2分别指向两个需要比较的元素。

🌈qsort()库函数使用例子代码🌈

//void qsort (void* base, size_t num,  size_t size,  int (*compar)(const void*, const void*));

//void*base :需要排序元素的首元素地址,用base指针来接收

 //size_t num:待排元素的个数
 
 //size_t size:每个元素所占用空间的大小,单位是字节

 //int(*compar)(const void*,consr void*):函数指针,因为每一种元素的排序方法是不一样的,需一个函数来实现比较,函数的返回类型是int

struct stu
{
	char name[20];
	char sex[20];
	int age;
};
int zx(const void* a, const void* b)//整数比较方法
{
	return (*(int*)a - *(int*)b);
}
int jgtname(const void* a, const void* b)//结构体name比较
{
	return  strcmp(((struct stu*)a)->name, ((struct stu*)b)->name);
}
int jgtage(const void* a, const void* b)
{
	return ((struct stu*)a)->age - ((struct stu*)b)->age;
}


void test1()
{
	printf("排序前:\n");
	int arr[] = { 2,9,5,7,6,8,2,4,4,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	qsort(arr, sz, sizeof(int), zx);//整形数组排序
	printf("排序后:\n");

	for( i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test2()
{
	struct stu s[] = { { "zhangsan","male",20 },{"lusiri","lady",8}, {"dengpeiqun","lady",48},{"luchaokun","male",57}};
	struct stu* ps = s;//这里加个指针只是单纯练习一下指针的使用
	int i = 0;
	int sz = sizeof(s) / sizeof(s[0]);
	printf("name排序前:\n");
	for (i = 0; i < sz; i++)
	{
		printf("%s %s %d", (ps+i)->name, (ps + i)->sex, (ps + i)->age);
		printf("\n");
	}
	printf("\n");
	printf("name排序后:\n");
	qsort(s, sz, sizeof(s[0]),jgtname );结构体数组name的排序
	for (i = 0; i < sz; i++)
	{
		printf("%s %s %d", s[i].name, s[i].sex, s[i].age);
		printf("\n");
	}
	qsort(s, sz, sizeof(s[0]), jgtage);//结构体数组age的排序
	printf("\n");
	printf("age排序后\n");
	for (i = 0; i < sz; i++)
	{
		printf("%s %s %d", s[i].name, s[i].sex, s[i].age);
		printf("\n");
	}
}


int main()
{  

	test1();
	printf("\n");
	test2();
	printf("\n");
	return 0;
}

🐠 🐟  qsort()库函数的模拟实现🐠 🐟 

💦冒泡排序

库函数qsort()的底层逻辑是快速排序,但是为了更好的理解我们这里选择用冒泡排序来模拟

什么是冒泡排序呢?

就是在一组数据中,通过两个两个数据的比较来达到我们排序的目的。

举个冒泡排序的例子吧:

我现在有一组数据   {5 、3 、4 、 1、 2};

现在我用冒泡排序将这组数排序成从小到大的有序数列;

5  3  4  1  2        第一次比较   5   3 ; 5>3,5向后移动  

5  4  1  2        第二次比较   5   4;  5>4,5向后移动

3  4  5  1  2        第三次比较   5   1;  5>1,5向后移动

3  4  1  5  2        第四次比较   5   2;  5>2,5向后移动

3  4  1  2  5        无需比较

我们可以看到经过一趟冒泡排序,我们把5移动到最后的位置上需要移动4次,也就是n-1次;(这已经是最坏的情况了)

需要多少躺冒泡排序?

5个元素最坏需要排序4次,找一下规律就知道是(n-1)次

每一趟冒泡排序要排多少次呢?

这是一个递减的过程,当5移动到最后时,就不需要再移动的,3 4 1 2就变成了一个新的需要冒泡排序的序列,最多需要排序n-1次;以此类推。

冒泡排序的代码


void bubble_sort( int *a,int sz)
{
	int i = 0;
	int j = 0;
	int temp = 0;
	for (i = 0; i < sz - 1; i++)    
	{
		for (j = 0; j < sz - 1 - i; j++)
		{   
			if (a[j] < a[j + 1])
			{
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
	printf("\n");
}

 ⚡️冒泡排序二代⚡️

我们看到上面的冒牌排序只能排序 int 类型的数组,而qsort函数却能排序多种类型的数据,所以在冒泡排序的基础上我们想将它优化成可以排序多种类型的冒泡排序二代。

void bubble_sort2(void*base,int sz,int width,int(*cmp)(const void *e1,const void *e2) )
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)//一趟冒泡排序
	{
		for (j = 0; j < sz - i - 1; j++)//每一趟冒泡排序的过程
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)//调用cmp函数做比较
			{
				swp((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

1.void*base

  指向排序的数组的第一个对象的指针。为什么是用void* 指针类型来接受呢?因为qsort()设计的时候可以排序多种类型的。但是这个函数的作者并不知道我们使用者需要排序什么类型的数据,传参的是什么类型的指针,void*类型的指针是比较宽容的,可以接收多种类型的指针。

 2.int size

       待排序元素的个数

 3.int width

每一个元素所占内存空间大小,单位是(字节)

4.int (*cmp )(const void*e1,const void*e2)

这是一个函数指针,指向我们需要调用的函数。这个调用的函数是用来设置比较方法的。为什么要设置比较的方法呢?因为我们每一种类型的比较方法是不一样的。整形(int)类型

我们知道要用 > 、<、 =、来比较,字符串就不可以用我们用 > 、<、 =、来比较了,strcmp()库函数来比较大小;还有一些其他的类型,所以我们要自己设置一个比较函数传参给qsort函数(回调函数的使用)。

e1和e2分别指向两个需要比较的元素。

 值得一提的是这里的转换。void*base是不能直接解引用的,我们需要转换成其他的类型后再解引用。但是设计者是不知道使用者需要传参的是什么类型的数据。所以我们转换成(char*)类型是最合适的,(char*)每一次加减都是偏移一个字节。我们传参的时候把width传过来了。char*)base + j * width 和 不就是(char*)base + (j + 1) * width不就是两个需要比较元素的首地址吗?

🍀swp函数🍀

void swp(char* buf1, char* buf2, int width)
{ 
	int d = 0;
	char tmp = 0;
	for (d = 0; d < width; d++)
	{
		tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

swp函数是用来交换元素位置的。参数是需要交换的两个元素的地址,还有每个数据占用内存空间大小。

原本的冒泡排序是直接元素交换的,但是我们现在不知道我们数据的类型,我们就拆分成一个字节一个字节的交换。

比如说我排序的是一个整形数组,那么传参的时候width就是4,因为int类型占用4个字节,分4次交换一个元素。

我排序一个double的数组,那么传参的时候width就是8,double占用8个字节。分8次交换一个元素。

所以swp()交换不同类型的元素,需要循环的次数是不一样的。

🌕完整模拟代码

//bobble_sort2的交换函数
void swp(char* buf1, char* buf2, int width)
{ 
	int d = 0;
	char tmp = 0;
	for (d = 0; d < width; d++)
	{
		tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
//在原来的qsort函数是可以排序任何数据类型的,
// 所以我们要改进我们的冒泡排序也是可以接收任何数据类型的指针
//
void bubble_sort2(void*base,int sz,int width,int(*cmp)(const void *e1,const void *e2) )
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)//一趟冒泡排序
	{
		for (j = 0; j < sz - i - 1; j++)//每一趟冒泡排序的过程
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)//调用cmp函数做比较
			{
				swp((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

void test1()
{
	int arr2[] = { 2,1,5,7,3,4,6,9,8,0 };
	int sz = sizeof(arr2) / sizeof(arr2[0]);
	bubble_sort2(arr2, sz, sizeof(arr2[0]), cmp_int);
	for (int i = 0; i < sz; i++)
	{
		printf(" %d ", arr2[i]);
	}
	printf("\n");
}
void test2()
{
	struct stu s[] = { {"xuxiaoyan",21},{"luzhimin",25}};
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort2(s,sz,sizeof(s[0]),cmp_structname);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s", s[i].name);
		printf("\n");
	}
}
int main()
{
	
	test1();
	test2();
	return 0;
}

🥥运行截图

制作不易,如有帮助请帮博主一键三连!!!蟹蟹!!  制作不易,如有帮助请帮博主一键三连!!!蟹蟹!! 

制作不易,如有帮助请帮博主一键三连!!!蟹蟹!! 制作不易,如有帮助请帮博主一键三连!!!蟹蟹!! 

制作不易,如有帮助请帮博主一键三连!!!蟹蟹!! 制作不易,如有帮助请帮博主一键三连!!!蟹蟹!! ​​​​​​​

  • 35
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

熬夜退役选手337

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

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

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

打赏作者

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

抵扣说明:

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

余额充值