C:qsort的模拟实现(2) --(超详解)

前言:

        hello,小伙伴们,之前我们了解了qsort函数的参数类型和使用方法,今天小编给大家完成qsort的模拟实现,由于本人实在能力有限,这里仅用冒泡排序来完成。话不多说,直接步入正题:


正文:

首先,我们还是先来分析qsort函数的参数类型,看看哪些参数是我们模拟函数需要的:

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

base是要排序的那个数组,这是需要的;num是这个数组的元素个数,也是需要的,size是该数组中一个元素的大小,好像也是需要的,那这个函数指针是干嘛的呢?我们后面说,这里先大概知道它指向我们要比较的数据类型的两个元素。

既然我们大概知道了参数,那我们就可以模拟qsort函数,实现一个用冒泡排序算法的qsort函数,这里就是bubble_sort了 。

接着,我们思考冒泡排序算法(升序):

一趟:不断交换两个数,让最大的数到最后,完成一趟冒泡排序;

第二趟:不断交换两个数,让第二大的数到最后,完成一趟冒泡排序;由于已经排好了一个数,所以交换的次数每次-1。

...

再第三趟,第四趟...直到排完所有趟数。升序完成

冒泡排序大概代码逻辑如下 :

int i = 0;
//一趟
for (i = 0; i < num-1; i++)
{
	//每一趟
	int j = 0;
	int flag = 1;//假设这一趟已经有序
	for (j = 0; j < num - 1 - i; j++)
	{
		if (arr[j] > arr[j + 1])//比较相邻两个数大小
			//交换
		{
			int tmp = arr[j];
			arr[j] = arr[j + 1];
			arr[j + 1] = tmp;
			flag = 0;
		}
	}
	if (flag == 1)
	{
		break;
	}
}

这里对冒泡排序优化了一下,每趟创建了一个flag来控制是否有序,如果有序,则直接退出冒泡排序,这对原数组已经有顺序的情况下,做出优化。 


看到这里,小伙伴们应该没有问题,那我们继续思考,冒泡排序的哪些逻辑需要变?因为我们是对任意数据排序,所以怎么判断两个数的大小,以及需要交换这两个数,怎么交换,我们在接受这个数组时,又该又什么样的指针,现在,我们一一来解决,其实很简单,不用害怕。

我们先解决怎么判断arr[j]>arr[j+1]?

这就不得不提到我们上面遗留的qsort的第四个参数int (*compar)(const void*,const void*),因为qsort函数是对任意类型的数据排序,那么我就需要有一个函数来实现使用者想要排序的数据类型,就像一个接口一样,使用者想排序整形,我们使用者就自已写一个比较整形两个数的逻辑,我们想比较字符型,使用者就自已写一个比较字符类型两个字符比较的逻辑,所以在比较arr[j]>arr[j+1]我们需要借助函数指针,我们使用者想比较什么,这个函数指针就指向什么

现在我们假设要排序整形,那么我们就实现一个比较两个整数的逻辑。

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

我们就arr[j]和arr[j+1]传过去,因为只想比较,不想改变,所以我用(const void*e1,const coid*e2)这两个参数接受。就是我们上一篇的内容 。如果cmp_int返回值>0,说明arr[j]>arr[j+1],说明需要交换;剩下两种情况相似,cmp_int返回值<=0,说明arr[j]<=arr[j+1],说明不需要交换。

由于base这个指针指向数组的第一个元素,那如果是字符型或其他类型的数据,我怎么找到arr[j]和arr[j+1]呢,我们知道,在内存在计算机中储存,就像char一样,至少是一个字节(byte),那么我们不是还有第三个参数,一个元素的宽度嘛。那我们就可以从base(数组的首元素)开始,跳过宽度*j个字节,强制类型转换为char就可以了,也就是这样:(char*)base + width * j,这样找到的就是第j个元素的地址;第j+1个元素的地址同理:(char*)base + width * (j+1);这样我们就找到任意数据类型两个元素任意位置的地址了,其实,接下来其他代码的实现,也是这个道理。

小编给大家画了几幅图,方便大家理解。

这样我们就能判断arr[j]>arr[j+1]的大小了。


看到这里你已经成功了80%了,接下来我们完成如果arr[j]>arr[j+1],交换的逻辑。

那由于上面的思路启发,我们就知道交换两个任意类型的数据,我们就一个一个字节交换,只需要知道交换每个字节的宽度,这就行了。如图:


完整代码测试整形,结构体类型升序: 

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
struct Stu
{
	int age;
	char name[10];
};
//自已实现结构体数据两个整数的比较
int cmp_struct_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//自已实现整形数据两个数的比较
int cmp_int(const void*e1,const void*e2)
{
	return *(int*)e1 - *(int*)e2;
}
//自已实现结构体数据两个字符串的比较
int cmp_struct_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name ,((struct Stu*)e2)->name);
}
//交换函数
void Swap(char* pos1, char* pos2,size_t width)
{
	while (width)
	{
		char tmp = *pos1;
		*pos1 = *pos2;
		*pos2 = tmp;
		pos1++; pos2++; width--;
	}
}

void bubble_sort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	//一趟
	for (i = 0; i < num-1; i++)
	{
		//每一趟
		int j = 0;
		int flag = 1;//假设这一趟已经有序
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp( (char*)base+width * j, (char*)base+(j+1)*width )>0)//比较相邻两个数大小
			{
				flag = 0;//无序
				//交换
				Swap((char*)base + width * j, (char*)base + (j + 1) * width, width);
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}
void print1(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void print2(struct Stu s1[],int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("name:%s --- age:%d\n", s1[i].name,s1[i].age );//结构体变量.成员名
	}
}
//用bubble_sort排序整形
void test1()
{
	int arr[10] = { 4,1,3,2,7,10,8,6,5,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print1(arr, sz);
}

//用bubble_sort排序结构体
void test2()
{
	struct Stu s1[] = { {20,"lisi"},{18,"wangwu"},{19,"zhangsan"}};
	int sz = sizeof(s1) / sizeof(s1[0]);
	printf("按年龄排序前:\n");
	print2(s1, sz);
	bubble_sort(s1, sz, sizeof(s1[0]), cmp_struct_age);
	printf("按年龄排序后:\n");
	print2(s1, sz);
	bubble_sort(s1, sz, sizeof(s1[0]), cmp_struct_name);
	printf("按名字排序后:\n");
	print2(s1, sz);
}


int main()
{
	test1();
	test2();
	return 0;
}

文末 :

到这里qsort的实现已经完成了,在小编刚接触函数指针的时候,也不知道怎么用,也觉得很神奇,没有头绪,但是,自已通过模仿,理解,写博客的方式再次加强了对这方面知识的理解深度,希望大家遇到困难不要害怕,困难只是一时的,慢慢来,不要心急,一定会克服的!

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值