上一次我们了解了qsort的相关用法,今天,我们就可以参考qsort,使用冒泡排序法的算法来实现一个可以排序任意类型数据的排序函数。
排序函数
目录
对冒泡排序法的回顾
关于冒泡排序法,我们上次已经复习了一边,这次我们为了接下来方便理解,我们再对其进行回顾。
冒泡排序法思想就是:两两相邻的元素进行比较,多次进行。
我们这里可以创建一个降序数组:
int arr[] = { 9,8,7,6,5,4,3,2,1};
我们知道,冒泡排序法一趟应该是这样:
一趟之后,9就不动了,之后就进行第二次冒泡排序,直到完全成为升序。
我们要注意的是:需要计算数组长度来确定冒泡排序的趟数,以及我们最后需要得到的是升序数组,从而对循环进行编写,完整代码如下:
void bubble_sort(int arr[], int sz) { int i = 0; for (i = 0; i < sz-1; i++) { int j = 0; 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; } } } } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void test1() { int arr[9] = { 9,8,7,6,5,4,3,2,1 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); print_arr(arr, sz); } int main() { test1(); return 0; }
但是这里的冒泡排序法只能排序整型,而我们今天的目标是排序任意类型的数据,所以我们将其进行改造 。
对冒泡排序法的排序函数进行修改
我们既然是要排序任意类型的数据,那么参数也应该可以接收任意数据的类型,这是我们自然而然的想到了void*,它是可以接收任意类型的数据的,所以我们就可以创建函数了:
void bubble_sort(void* base, int sz)
这里就会出现一个问题,void*的出现,使我们无法确定数据当中元素的类型,因此我们就需要知道元素的大小,这里我们参考qsort的参数对其修改,但我们会注意到,qsort的参数是有比较大小的函数的,我们这里既然也是排序任意类型的数据,那么我们也应该需要这么一个函数,我们如下创建:
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void* e2))
接下来就是对冒泡排序法中的排序函数进行修改了,首先,冒泡的趟数是不会随着数据类型的改变而改变的,我们只需要将元素的比较函数进行修改,在元素比较完成后,我们将接收的返回值与0比较,再进行排序。但是,由于参数是void*,我们只能通过两个元素的地址来对其进行比较。正因为是void*,我们需要进行强制转换,那么我们应该如何进行转换才能表示我们所有的元素的地址?瞄一眼我们的参数:base,数据首元素的地址;num,数据元素个数;size,数据元素的大小以及最后的比较大小的函数。我们就可以想到char*,char*的大小是一个字节,而我们又有元素的个数和每个元素的大小,正好,用char*就可以将所有元素的地址表示出来,我们如下创建:
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
如果返回值>0,那么我们就需要将两个元素进行交换,没错,那个问题又出现了,我们无法确定元素的类型,但是我们有base和size,我们就可以一个字节一个字节交换,这样也可以完成两个元素之间的交换,于是我们就创建交换的函数:
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
这里,我们已经进行过强制转换了,我们就不需要再将参数定义为void*了,由于是一个字节一个字节进行交换,那么我们就可以创建一个字节的临时变量,代码如下:
void swap(char* buf1, char* buf2, size_t size) { int i = 0; for (i = 0; i < size; i++) { char tmp = *buf1;//对buf1找到的字节进行解引用 *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } }
对整型数组所在的函数进行修改
这里我们只需要对着排序函数的参数依次进行修改就可以了,我们重新创建一个数组,代码如下:
void test1() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 };//升序 int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), com_int); print_arr(arr, sz); }
那么如何修改比较整型的函数呢?与上次一样,我们只用将void*强制类型转换就可以了,这里既然是整型我们就强制转换成int*再解引用就可以了,代码如下:
int com_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; }
运行结果如下:
完整代码:
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* buf1, char* buf2, size_t size) int i = 0; for (i = 0; i < size; i++) { char tmp = *buf1;//对buf1找到的字节进行解引用 *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void* e2)) int i = 0; for (i = 0; i < num - 1; i++) { int j = 0; for (j = 0; j < num - i - 1; 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 com_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } void test1() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 };//升序 int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), com_int); print_arr(arr, sz); } int main() { //使用bubble_sort来排序整型数据 test1(); return 0; }
对结构体的比较
既然是对结构体的比较,那么我们先创建一个结构体:
struct Stu { char name[20]; int age; };
接下来就是赋值和引用函数:
void test2() { struct Stu arr[] = { {"zhangsan",25},{"lisi",30},{"wangwu",15} }; int sz = sizeof(arr) / sizeof(arr[0]);//sizeof是可以来计算结构体数组的元素个数的 bubble_sort(arr, sz, sizeof(arr[0]), com_stu_age);//传参方式就需要做出改变(这里是年龄) }
与上次相同,我们就分别用年龄和名字来进行比较,由于排序函数我们已经进行说明,我们接下来就只需要创建比较大小的函数。
通过年龄进行比较
与整型类似,我们只需要将void*强制转换成结构体指针类型再解引用就可以了:
int com_stu_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; }
运行结果如下:
通过名字进行比较
年龄是字符串,我们这里需要使用到strcmp函数来对字符串进行比较,我们上次知道,strcmp的返回值正好是int,于是我们如下创建:(不了解的读者可以访问qsort - C++ Reference (cplusplus.com))
int com_stu_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); }
运行结果如下:
小结
今天,我们完成了用冒泡排序的算法来创建排序函数,到了这里,我们对指针的进一步了解也就差不多完结啦。之后就是会指针笔试的分析了,在我们学习完后,一定需要加以复习,温故而知新。另外,由于小编的水平实在有限,出现的逻辑问题请务必指出,好啦,我们下次见!