一.冒泡排序
冒泡排序是C语言中众多排序中的一种。它的排序逻辑为(升序):从第一个元素开始和相邻的比较,如果第一个元素大于第二个元素,则交换,反之不交换;第二个再与第三个元素比较,第二个大于第三个交换,反之不交换……一趟之后,最大的元素将会出现在数组的最后一个;下来再重头开始,继续比较。
我们可以看到,一趟交换,就会将最大的元素放在数组的最后,我们在进行下一趟交换,就可以把次大值放在数组倒数第二个位置。这就是冒泡排序的基本逻辑,下来我们用代码来实现:
//冒泡排序
void bubble_sort(int* nums, int numsSize)
{
//趟数
int i = 0;
for (i = 0; i < numsSize - 1; i++)
{
//每一趟需要交换的次数
int j = 0;
for (j = 0; j < numsSize - i - 1; j++)
{
//前一个大于后一个就进行交换
if (nums[j] > nums[j + 1])
{
int tmp = nums[j + 1];
nums[j + 1] = nums[j];
nums[j] = tmp;
}
}
}
}
二.qsort函数
我在上一期博客已经详细的介绍了qsort函数,我们现在来复习一下qsort函数的功能、参数及返回值。
qsort函数的功能就是快速排序一任意类型数组。
而它的参数分别为:待排序数组,数组的大小,数组中每个元素的大小,比较函数。
qsort函数无返回值。
我们知道了qsort函数的功能及参数后,我们就可以在上面写的冒泡排序函数上进行修改,以此达到模拟qsort函数的功能。
三.模拟qsort函数
首先我们是依据冒泡排排序来进行模拟的,而qsort函数本来使用的快速排序。本质上都是排序,我们可以知道,冒泡排序的趟数和每趟交换的次数是不用进行修改的。修改的有:比较两相邻元素的大小、两元素的交换、以及函数参数。
1.函数参数的修改
既然要模仿qsort函数,所以我们应该保持函数参数的一致。
//nums 待排序数组
//numsSize 待排序数组的大小
//width 数组中每个元素的大小
//int(*compare)(const void*, const void*) 比较函数
void bubble_sort(void* nums, size_t numsSize, size_t width, int(*compare)(const void*, const void*))
{
}
2.修改两相邻元素之间的比较方式
因为qsort函数可以比较任意类型的数组,所以传递数组的类型为void*型。而在上一期博客我已经介绍到,两元素之间的比较应该由该函数的使用者来编写,因为使用者肯定知道他们排序的是什么类型的数组。所以,之前冒泡排序中的if语句中的判断条件就应该修改为比较函数对两相邻元素的比较的返回值。比较函数的返回值为整型,>0交换 =0不交换 <0也不交换(默认排序后为升序)。
//比较函数
if (compare((char*)nums + j * width, (char*)nums + (j + 1) * width))
{
}
我们可以先将待比较的两个元素强制转换为char*类型,但是这样的话就会改变原来元素的类型
所以我们再给强转之后的结果乘上数组每个元素的大小即width,这样每个指针访问的时候访问的都是原来数组类型的字节个数
例如:待排序数组为int型,转为char*后,如果直接传给比较函数,就会造成数据的截断,因为整型每个元素占四个字节
我们给其乘上width之后,就相当于指针向后走了width个位置,就相当于指向了下一个整型的位置。
但我们知道,比较的时候,要一直往后走进行比较,所以我们可以给width乘上一个j变量,j = 0,相当与第一个整型,j = 1,相当于跳过了一个width的长度,即一个整型,j = 2的时候,相当于跳过了两个整型。所以,这样就可以比较任意类型的两元素。
3.两元素的交换
之前的冒泡排序只能交换整型,而我们想交换其他类型的时候往往不适用,所以我们可以将元素的交换抽象成一个函数,利用该函数对元素进行交换。
void Swap(char* p1, char* p2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char c = *p1;
*p1 = *p2;
*p2 = c;
p1++;
p2++;
}
}
假设我们交换的是整型数据,但传过来的却是字符型,这怎么交换呢?
这幅图就是Swap函数交换的逻辑。我们可以一个字节一个字节的进行交换,因为我们将每个元素的大小传给了Swap函数,所以可以据此利用循环的方式,将其每一个字节都进行交换。
四.利用模拟函数进行整型数据的排序
综合上面的内容,我们就可以写出以冒泡排序为基础的qsort函数的模拟函数。
void bubble_sort(void* nums, size_t numsSize, size_t width, int(*compare)(const void*, const void*))
{
int i = 0;
for (i = 0; i < numsSize; i++)
{
int j = 0;
for (j = 0; j < numsSize - 1 - i; j++)
{
if (compare((char*)nums + j * width, (char*)nums + (j + 1) * width) > 0)
{
Swap((char*)nums + j * width, (char*)nums + (j + 1) * width, width);
}
}
}
}
我们现在利用此函数,进行整型数组的排序:
#include <stdio.h>
//比较函数
int compare_int(const char* p1, const char* p2)
{
return *(int*)p1 - *(int*)p2;
}
//交换函数
void Swap(char* p1, char* p2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
//模拟函数
void bubble_sort(void* nums, size_t numsSize, size_t width, int(*compare)(const void*, const void*))
{
int i = 0;
for (i = 0; i < numsSize; i++)
{
int j = 0;
for (j = 0; j < numsSize - 1 - i; j++)
{
if (compare((char*)nums + j * width, (char*)nums + (j + 1) * width) > 0)
{
Swap((char*)nums + j * width, (char*)nums + (j + 1) * width, width);
}
}
}
}
//打印函数
void Print(int* nums, int numsSize)
{
int i = 0;
for (i = 0; i < numsSize; i++)
{
printf("%d ", nums[i]);
}
}
void test()
{
int nums[] = { 10,9,8,7,6,5,4,3,2,1 };
int numsSize = sizeof(nums) / sizeof(nums[0]);
bubble_sort(nums, numsSize,sizeof(nums[0]),compare_int);
Print(nums, numsSize);
}
int main()
{
//模拟qsort函数
test();
}
结果为:
五.结语
到此,利用冒泡排序模拟qsort函数已经完成,要想排序不同的类型只需改变比较函数即可(上期博客已介绍,故不在此赘述)。