一、分析qsort函数
库函数qsort可以对任意的数据类型进行快速排序,只是在排序的规则上与冒泡排序有所区别,所以我们可以先对qsort函数进行分析,对实现冒泡排序任意类型会有所帮助。
从下图可以看到qsort函数有4个参数。其中,base是待排序的一组相同类型数据的起始地址,因为是对任意类型的数据进行排序,不能用一个固定类型的指针来接收,所以选择用void*这一无具体类型指针来接收;num是这一组数据的的个数;width是每个数据所占空间的大小(单位为字节);compare是一个函数指针,指向一个形参类型为两个const void*, 返回类型为int的函数,该函数实现的是两个数据的比较规则,由于要实现任意类型数据的比较,为了统一函数指针的类型,所以函数的参数类型统一用const void*。
![](https://i-blog.csdnimg.cn/blog_migrate/4eb7f4754944c206296be2dc1bcfcf18.png)
下面我们利用qsort对一组浮点型数据进行排序
#include <stdio.h>
#include <stdlib.h>
int cmp_double(const void* e1, const void e2)
{
double ret = *(double*)e1 - *(double*)e2;
if (ret > 0)
return 1;
else if (ret < 0)
return -1;
else
return 0;
}
int main()
{
double arr[] = { 1.9, 1.3, 1.1, 1.2, 1.7, 1.0, 1.5, 1.8, 1.4, 1.6 };
int len = sizeof arr / sizeof (double);
int(*cmp)(const void*, const void*) = cmp_double;
qsort(arr, len, sizeof (double), cmp);
//1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9
return 0;
}
二、实现bubbleSort函数
通过上面对qsort函数的大致分析,我们了解到对一组任意类型数据进行排序所需要的参数,下面我们来实现冒泡排序任意类型数据。
void bubbleSort(void* base, size_t num, size_t width,
int(*cmp)(const void* e1, const void* e2))
{
for (size_t i = 0; i < num - 1; i++)
{
int flag = 1;
for (size_t j = 0; j < num - 1 - i; j++)
{
//利用width可以实现两个指针分别指向前后两个元素
char *p1 = (char*)base + j * width,
*p2 = (char*)base + (j + 1) * width;
if (cmp(p1, p2) > 0)//p1指向的元素大于p2指向的元素
{
flag = 0;
//以字节为单位交换两个数据
for (size_t k = 0; k < width; k++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
}
if (flag)
return;
}
}
不难看出,参数num让我们明确了排序的次数以及每次排序需要比较多少对元素;参数width明确了前后两个指针相隔的距离,以保证在比较函数中解引用时访问到相应空间大小的元素,以及实现以字节为单位对两个元素进行交换;参数cmp是实现排序任意类型的关键,该指针指向与排序的数据类型对应的比较函数,不同的数据类型对应不同的比较函数。
- 整型数据对应的比较函数
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
- 浮点型数据对应的比较函数
int cmp_double(const void* e1, const void* e2)
{
double ret = *(double*)e1 - *(double*)e2;
//防止浮点型向下取整
if (ret > 0)
return 1;
else if (ret < 0)
return -1;
else
return 0;
}
注意:这里之所以没有像整型比较一样返回两个元素的差值,是因为浮点型数据作为int类型返回时会向下取整(如0.1会以0进行返回)
- 结构体类型数据对应的比较函数
以下面这一结构体类型为例,按姓名首字母对一组结构体数据进行排序。
struct Stu
{
char name[20];
int age;
};
int cmp_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
注意:对两个字符串进行比较,需要使用strcmp函数,将两个字符串的首字符地址传入该函数。
以上是3种不同类型数据对应的比较函数,函数返回值大于0时表示指针e1指向的元素大于指针e2指向的元素,如果数据按升序排序,我们就需要交换两个元素的位置;函数返回值小于0时表示指针e1指向的元素小于指针e2指向的元素;函数返回值小于0时表示两个元素相等。设计好相应的比较函数,我们就可以实现冒泡排序一组任意类型的数据了。