目录
一、qsort函数
qsort是一个现成的库函数,能够排序任意类型的数据。
我们来分析一下qsort函数的形参列表里各个参数是什么意思:
void qsort(void* base, //指向待排序数组的第一个元素
size_t num, //待排序的元素个数 (size_t是无符号整型)
size_t width, //每个元素的大小,单位是字节
int(__cdecl* compare)(const void* elem1, const void* elem2) //指向一个函数,这个函数可以比较2个数的大小
);
这里我们来演示一下qsort函数是如何使用的,以一个无序的整型数组为例,我们调用qsort函数使其按升序排序:
#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void print_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 3,1,5,9,2,4,7,6,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
return 0;
}
这里我们要注意的是:使用qsort函数进行排序时,需要我们自己提供一个比较函数来比较2个整型数据,也就是上述代码里的cmp_int。我们可以注意到比较函数的形参类型为void*,void*类型是不可以直接解引用的,我们要先将其强制类型转换后再解引用。
二、冒泡排序算法
冒泡排序是一种简单的排序算法,通过重复地遍历要排序的数组,比较相邻的两个元素,如果它们的顺序不正确就交换它们,直到整个列表排序完成。
以一个无序整型数组为例,我们想让它以升序排序:
一个数组共10个元素,每一趟排序可以确定1个数字。
第一趟冒泡排序,共10个数字进行排序。冒泡排序的核心思想是两两相邻的元素进行比较,进行9对数字的比较后即可确定出这个数组中最大的数字9。
第二趟冒泡排序,共9个数字进行排序,进行8对数字的比较后即可确定出这9个数字中最大的数字8。
以此类推,我们可以知道,冒泡排序的趟数=元素个数-1,每一趟里面共进行元素个数-1-趟数次对比。
#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
int i = 0;
int j = 0;
//冒泡排序的趟数
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 3,1,5,9,2,4,7,6,8,0 };
//冒泡排序 - 升序
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
三、以冒泡排序思想模拟实现qsort函数
在上面我们已经提到过,qsort函数能够排序各种类型的函数,而冒泡排序只能排序一种类型的数据,此时我们若想以冒泡排序思想模拟实现qsort函数,最重要的一点就是使bubble_sort函数能够排序各种类型的数据。
根据上面的 qsort 的参数列表,我们可以将 bubble_sort 函数的参数列表改成:
void bubble_sort (void* base, size_t num, size_t width, int (*cmp)(const void* p1, const void* p2))
由于 num 和 width 都是 size_t 类型的数据,我们在定义变量 i 和 j 的时候,也可以将其定义成 size_t 类型。
在每一趟的冒泡排序当中,我们都要进行相邻元素的比较,我们可以将其传递给 cmp_arr 函数,也就是我们自定义的比较函数:
我们可以注意到 cmp_arr 函数的形参类型是 void* 类型,void*类型是不可以直接解引用的,由于我们比较的是 int 类型的数据,可以先将 p1 和 p2 强制类型转换成 int* 类型的,再解引用,就可以得到两个数字了。如果两数相减返回的数值大于0,则说明前一个数比后一个数大,而我们想要这个数组按照升序排序,就要将两个数字进行交换,此时我们可以定义一个交换函数:
由于我们在定义 bubble_sort 函数的第一个形参时,base 是 void* 类型的,并且在后续的使用中,我们希望 bubble_sort 是个通用的排序函数,可以排序任意类型的数据,所以我们可以将 base 强制转换为 char* 类型的,因为 char 类型只占一个字节,再根据数据原本的类型所占字节大小,将其作为参数传给 Swap 函数,两个 char* 指针分别指向要交换的两个数据,width 即是中间需要进行交换的内容的长度。
如果 cmp_arr 函数的返回值等于0,则说明这两个数字一样大;如果小于0,则说明前一个数字小于后一个数字,不用进行交换。
在以上的思路上,我们可以再做一个改进:定义一个标记变量flag,判断数组是否已经有序,若已经有序,就可以直接跳出循环。
四、最终代码呈现
以冒泡排序思想模拟实现qsort函数(以升序排序)
#include<stdio.h>
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void Swap(char* p1, char* p2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
int cmp_arr(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void bubble_sort(void* base, size_t num, size_t width, int (*cmp)(const void* p1, const void* p2))
{
size_t i = 0;
size_t j = 0;
//确定冒泡排序的趟数
for (i = 0; i < num - 1; i++)
{
int flag = 0; //标记变量,判断数组是否已经有序
//一趟冒泡排序的过程
for (j = 0; j < num - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
flag = 1;
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
if (flag == 0)
{
break;
}
}
}
int main()
{
int arr[] = { 3,1,5,9,2,4,7,6,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_arr);
print_arr(arr, sz);
return 0;
}