模拟实现通用型排序

在这里插入图片描述

本期介绍🍖
主要介绍:什么是泛型排序,即:无类型排序,以及库函数qsort()的使用,以及如何自己模拟实现一个泛型的冒泡排序。



1. 什么是通用型排序

  之前我们实现过冒泡排序,如下所示。但该排序只能对int型数组进行排序,因为在编写这个函数的时候,就是以排序整型来实现的。如若想排序其他类型的数据,只能重新实现排序函数。那有没有什么办法,让一个排序函数能够排序所有类型的数据呢?有的,那就是:通用类型排序

//冒泡排序
void bubble_sort(int arr[], int num)
{
	//趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//每趟两两交换次数
		int j = 0;
		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;
			}
		}
	}
}

2. 库函数qsort()

2.1 定义

  在C语言的库中就存在着一个函数qsort()快速排序,该排序就是上述的通用类型排序,使用前需要引用头文件stdlib.h。函数声明定义如下:

void qsort (void* base, size_t num, size_t size, 
			int(*cmp)(const void* p1, const void* p2));

  qsort()函数的返回类型为void,表示没有返回值,函数有4个参数,如下所示:

  1. base:是void*类型的指针,该指针指向待排序数组的起始位置。
  1. num:是待排序数组中元素的个数
  1. size:是待排序数组中每个元素的大小(单位:字节)
  1. cmp:是函数指针,指针的类型为int(*)(const void* p1, const void* p2)),该指针指向的函数返回类型为int,函数有两个参数p1和p2,都为泛型指针且类型都是const void*。该函数会被qsort()调用,用于比较两个元素的大小,并返回值,返回值的规则如下所示。

在这里插入图片描述

  如果p1指向的元素>p2指向的元素,那么返回一个大于0的数。如果p1指向的元素<p2指向的元素,那么返回一个小于0的数。如果p1指向的元素=p2指向的元素,那么返回值为0。


2.2 使用

  首先需要了解,qsort()函数的参数cmp指向的那个比较函数,是需要qsort()函数的调用方自己实现的。因为不同类型数据的比较方式是完全不一样的,就比如char、int、float、double可以使用>、<、=操作符比较大小,但字符串就不行,结构体也不行。所以只能是qsort()函数的调用方自己实现,然后将实现的比较函数的地址作为参数传给qsort()函数,qsort()函数在某个时机返回来调用比较函数,这就是回调函数的用法。qsort()函数实际使用如下:

  1. 排序整型数组
#include<stdio.h>
#include<stdlib.h>

void print(int* parr, size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		printf("%d ", parr[i]);
	}
	printf("\n");
}

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

int main()
{
	int arr[] = { 3,1,4,5,7,2,9,8,0,6 };
	size_t num = sizeof(arr) / sizeof(arr[0]);
	size_t size = sizeof(arr[0]);
	qsort(arr, num, size, cmp_int);
	print(arr, num);
	return 0;
}

在这里插入图片描述

  1. 排序字符串
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void print(char* arr[], size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		printf("%s\n", arr[i]);
	}
}

int cmp_str(const void* p1, const void* p2)
{
	return strcmp(*(char**)p1, *(char**)p2);
}

int main()
{
	char* arr[] = { "zhangsan", "lisi", "wangwu"};
	size_t num = sizeof(arr) / sizeof(arr[0]);
	size_t size = sizeof(arr[0]);
	qsort(arr, num, size, cmp_str);
	print(arr, num);
	return 0;
}

在这里插入图片描述

  1. 排序结构体数组
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct stu
{
	char name[20];
	int age;
}stu;

void print(stu* parr, size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		printf("%s %d\n", parr[i].name, parr[i].age);
	}
	printf("\n");
}

int cmp_stu_age(const void* p1, const void* p2)
{
	return ((stu*)p1)->age - ((stu*)p2)->age;
}

int main()
{
	stu arr[] = { {"zhangsan", 25},{"lisi", 38},{"wangwu", 17} };
	size_t num = sizeof(arr) / sizeof(arr[0]);
	size_t size = sizeof(arr[0]);
	qsort(arr, num, size, cmp_stu_age);
	print(arr, num);
	return 0;
}

在这里插入图片描述


3. 模拟实现通用类型的冒泡排序

  根据qsort()函数,模拟实现一个通用类型的冒泡排序。由于无法得知需要排序数组的类型,冒泡排序函数接受待排序数组的地址的类型就不确定,故只能通过泛型指针void*来接收。
  值得注意,void*类型的指针是无具体类型的,能够存放所有类型的地址,但无法对其进行解引用*和加减整数+、-操作。因为无法确定其指向元素的类型,所有对其进行解引用该访问多大空间不确定,加减整数操作步长是多大也不确定。冒泡函数的声明如下:

void bubble_sort (void* base, size_t num, size_t size, 
				int(*cmp)(const void* p1, const void* p2));

  问:为什么通用类型排序需要知道待排序数据元素的大小呢?带着这个问题接着往下看。
  通用类型的冒泡函数与原冒泡函数相比,排序的算法思路是不变的,依然是相邻两个元素进行两两比较,一共要进行n-1趟。故冒泡排序代码的算法框架是不需要改变的。如下所示:

void bubble_sort (void* base, size_t num, size_t size, 
				int(*cmp)(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//每趟两两交换次数
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if ()//比较两个元素的大小
			{
				//交换两个元素
			}
		}
	}
}

  当执行到if(……),该如何实现比较两个元素的大小?由于待比较的两个元素的类型不确定,比较的方法自然也不同。所以只能是调用函数方,自己实现比较函数,并将函数的地址作为参数传递进来。但是问题来了,由于此时指向函数起始位置指针basevoid*类型的,无法进行加减整数操作,那么调用函数的时候,待比较的两个元素的地址该如何获得?
  如果待比较的两个元素是int型,那么加减整数一次就需要跳过1个整型4个byte。但是我们在实现排序函数时,是不知道两个元素是int型的,所以不能将base指针强制类型转化为(int*)。该怎么办呢?
  解决方案:将base指针强制类型转换为(char*)类型,那么此时base指针加减1就能跳过1个byte。想要找到元素的地址,就只需要将base + (下标)*(元素的大小)即可。如下所示:

void bubble_sort(void* base, size_t num, size_t size, int cmp(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//每趟两两交换次数
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				//……
			}
		}
	}
}

  继续往下执行,需要实现交换两个元素。但问题来了,由于不确定交换的两个元素的类型,无法通过解引用操作一次性访问整个元素来进行交换。该怎么解决?
  解决方案:先将元素的地址强制类型转化为(char*),一个字节一个字节的进行交换,一共交换size元素大小次,就能完成两个元素的交换了。将交换两个元素封装成一共函数,并将两个元素的地址和元素的大小作为参数传递给函数。如下所示:

void swap(void* s1, void* s2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)s1 + i);
		*((char*)s1 + i) = *((char*)s2 + i);
		*((char*)s2 + i) = tmp;
	}
}

void bubble_sort(void* base, size_t num, size_t size, int cmp(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//每趟两两交换次数
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

  下面来验证一下模拟实现的通用冒泡排序函数是否可行。

  1. 排序整型

#include<stdio.h>

void swap(void* s1, void* s2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)s1 + i);
		*((char*)s1 + i) = *((char*)s2 + i);
		*((char*)s2 + i) = tmp;
	}
}

void bubble_sort(void* base, size_t num, size_t size, int cmp(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//每趟两两交换次数
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

void print(int arr[], size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int num = sizeof(arr) / sizeof(arr[0]);
	int size = sizeof(arr[0]);
	bubble_sort(arr, num, size, cmp_int);
	print(arr, num);
	return 0;
}

在这里插入图片描述

  1. 排序结构体
#include<stdio.h>
#include<string.h>

typedef struct stu
{
	char name[20];
	int age;
}stu;

void swap(void* s1, void* s2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)s1 + i);
		*((char*)s1 + i) = *((char*)s2 + i);
		*((char*)s2 + i) = tmp;
	}
}

void bubble_sort(void* base, size_t num, size_t size, int cmp(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//每趟两两交换次数
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

void print(stu arr[], size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	printf("\n");
}

int cmp_stu_name(const void* p1, const void* p2)
{
	return strcmp(((stu*)p1)->name, ((stu*)p2)->name);
}

int main()
{
	stu arr[] = { {"zhangsan", 25},{"lisi", 38},{"wangwu", 17} };
	size_t num = sizeof(arr) / sizeof(arr[0]);
	size_t size = sizeof(arr[0]);
	bubble_sort(arr, num, size, cmp_stu_name);
	print(arr, num);
	return 0;
}

在这里插入图片描述


在这里插入图片描述
这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值