C 语言实现任意类型冒泡排序(C achieve bubble sort of any type)

 

以下假设都是升序排序。

1.  初学C语言的时候,第一个学的排序就是冒泡排序。

那什么是冒泡排序呢?大家可以参考资料 冒泡排序——菜鸟教程 里面写的很详细。总结2点:

(1)依次比较相邻元素,如果后面的元素比前面的元素小,那么就交换位置,直至这一轮结束。

(2)经过每一轮的比较,都会使大的值沉淀在后面。

说明图:

 

2.  C语言中快速排序函数 qsort

通过使用 MSDN 查看 qsort 使用方法(细品):

void qsort( void *base,

      size_t num,

      size_t width,

              int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

Return Value None

Parameters

base:Start of target array

num:Array size in elements

width:Element size in bytes

compare:Comparison function 

elem1:Pointer to the key for the search

elem2:Pointer to the array element to be compared with the key

Remarks

The qsort function implements a quick-sort algorithm to sort an array of num elements, each of width bytes. The argument base is a pointer to the base of the array to be sorted. qsort overwrites this array with the sorted elements. The argument compare is a pointer to a user-supplied routine that compares two array elements and returns a value specifying their relationship. qsort calls the compare routine one or more times during the sort, passing pointers to two array elements on each call:

compare( (void *) elem1, (void *) elem2 );

The routine must compare the elements, then return one of the following values:

Return Value Description
< 0 elem1 less than elem2
0 elem1 equivalent to elem2
> 0 elem1 greater than elem2

 The array is sorted in increasing order, as defined by the comparison function. To sort an array in decreasing order, reverse the sense of “greater than” and “less than” in the comparison function.

 

3.  C 语言实现任意类型的冒泡排序法

(1)冒泡排序代码实现

 1 void Swap(char* buf1, char* buf2, int width) {
 2     int i = 0;
 3     for (i = 0;i < width;i++) {
 4         char tmp = *buf1;
 5         *buf1 = *buf2;
 6         *buf2 = tmp;
 7         buf1++;
 8         buf2++;
 9     }
10 }
11 void bubble_sort(void* base, int sz, int width, int (*cmp)(void* e1, void* e2)) {
12     int i = 0;
13     // 趟数
14     for (i = 0;i < sz - 1;i++) {
15         // 每一趟比较的对数
16         int j = 0;
17         for (j = 0;j < sz - 1 - i;j++) {
18             // 两个元素的比较
19             if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) {
20                 // 交换
21                 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
22             }
23         }
24     }
25 }

代码讲解:

冒泡排序实现函数 bubble_sort,接收的参数有4个

第一个参数:待排序数组的首元素地址,因为类型可以是任意,所以这里用 void* 接收(void* 类型的指针,可以接收任意类型的地址);

第二个参数:待排序数组的元素个数;

第三个参数:待排序数组的每个元素的大小 —— 单位是字节,传入这个参数主要是因为 void* 类型的指针,不能进行解引用操作,那么当比较2个元素的大小的时候,你就不知道要比较多少个字节,所以需要传入要访问的数据的宽度,字节数;

第四个参数:函数指针,比较两个元素的所用函数的地址 —— 这个函数使用者自己实现,函数指针的两个参数是:待比较的两个元素的地址。因为比较的类型不确定,所以就把这部分封装成函数,以及要实现递增还是递减都可以用户自己设计。

 

主程序部分就是先循环每一趟,然后再循环每一趟要比较的次数,详细的设计说明可以看说明图理解,最后,调用 cmp 函数对 2 个数进行比较,如果 elem1 > elem2,那么 cmp 调用的结果就会 >0,那么就交换 2 个元素。

注意,这里传入的2个参数:(char*)base + j * width 和  (char*)base + (j + 1) * width 理解:这里把 base 的 void* 类型强制转换成了 char* 类型,因为 char 类型是1个字节,而 width 的单位也是1个字节,设计成 char 就可以直接和 width 进行加减运算。前一个值和后一个值进行比较,所以是 j * width 和 ( j + 1 ) * width

 

接下来看下交换函数 Swap,这里要注意的是,因为我们不知道传入的类型,所以不知道要交换多少个字节,所以需要传入 width,并且传入把 base 数据类型强制转换成 char*,这样才能使用 width 值就可以知道要交换多少次的字节了。

 

(2)int 类型测试代码:

1 int cmp_int(const void* e1, const void* e2) {
2     return *(int*)e1 - *(int*)e2;  // 强制类型转换后才能对void*解引用
3 }
4 void test_int() {
5     int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
6     int sz = sizeof(arr) / sizeof(arr[0]);
7     bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
8 }

代码讲解:

这里主要了解的是 cmp_int 函数的书写,这里先关注用的是 e1 - e2,即第一个元素比第二个元素大的话就会导致结果返回 >0 的数,而 >0 的结果会使两数交换,所以可知该函数实现的是升序排序。

其次,因为此时已经知道比较的类型是 int 型,所以就把 e1 和 e2 强制类型转换成 int* ,再对其进行解引用得到数值比较大小

 

(3)float 类型测试代码:

1 int cmp_float(const void* e1, const void* e2) {
2     return ((int)(*(float*)e1 - *(float*)e2));
3 }
4 void test_float() {
5     float f[] = { 9.0,8.0,7.0,6.0,5.0,4.0 };
6     int sz = sizeof(f) / sizeof(f[0]);
7     bubble_sort(f, sz, sizeof(f[0]), cmp_float);
8 }

代码讲解:

此时把 e1 和 e2 类型强制转换成 float* 型再对其进行解引用得到数值比较大小

 

(4)struct 类型代码测试:

 1 struct Stu {
 2     char name[20];
 3     int age;
 4 };
 5 int cmp_stu_by_age(const void* e1, const void* e2) {
 6     return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
 7 }
 8 int cmp_stu_by_name(const void* e1, const void* e2) {
 9     // 比较名字就是比较字符串
10     // 字符串比较不能直接用><=来比较,应该用strcmp函数
11     return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
12 }
13 void test_struct() {
14     struct Stu s[3] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 10} };
15     int sz = sizeof(s) / sizeof(s[0]);
16     bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
17     //bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
18 }

代码讲解:

当需要按照年龄进行升序排序的时候,那么就把 e1 和 e2 类型强制转换成 struct Stu* 类型,然后通过结构体指针访问成员的方式 -> 定位到需要排序的成员。

如果是需要按照名字进行升序排序,那么要注意,字符串的比较不能用 >=< 符号来进行比较,而是用使用 strcmp 函数来进行比较。

 

(5)回调函数

关于 bubble_sort 函数,关键一个点讲就是第四个参数比较函数是要抽离出来的,因为不同类型的数据的比较方法是不一样的,所以就把这个比较方法抽象成一个函数抽离出来,每个人都可以根据自己的需要传入需要比较的内容,之后再根据传过来的地址去调用所指向的函数。我们在函数内部通过函数指针去调用这个函数的时候,这个函数就被称为回调函数。这种机制就被称为回调函数机制。

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

 

(6)总代码:

 1 #define _CRT_SECURE_NO_WARNINGS 1
 2 #include <stdio.h>
 3 #include <string.h>
 4 
 5 void Swap(char* buf1, char* buf2, int width) {
 6     int i = 0;
 7     for (i = 0;i < width;i++) {
 8         char tmp = *buf1;
 9         *buf1 = *buf2;
10         *buf2 = tmp;
11         buf1++;
12         buf2++;
13     }
14 }
15 void bubble_sort(void* base, int sz, int width, int (*cmp)(void* e1, void* e2)) {
16     int i = 0;
17     // 趟数
18     for (i = 0;i < sz - 1;i++) {
19         // 每一趟比较的对数
20         int j = 0;
21         for (j = 0;j < sz - 1 - i;j++) {
22             // 两个元素的比较
23             if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) {
24                 // 交换
25                 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
26             }
27         }
28     }
29 }
30 
31 int cmp_int(const void* e1, const void* e2) {
32     return *(int*)e1 - *(int*)e2;  // 强制类型转换后才能对void*解引用
33 }
34 void test_int() {
35     int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
36     int sz = sizeof(arr) / sizeof(arr[0]);
37     bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
38 }
39 
40 int cmp_float(const void* e1, const void* e2) {
41     return ((int)(*(float*)e1 - *(float*)e2));
42 }
43 void test_float() {
44     float f[] = { 9.0,8.0,7.0,6.0,5.0,4.0 };
45     int sz = sizeof(f) / sizeof(f[0]);
46     bubble_sort(f, sz, sizeof(f[0]), cmp_float);
47 }
48 
49 struct Stu {
50     char name[20];
51     int age;
52 };
53 int cmp_stu_by_age(const void* e1, const void* e2) {
54     return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
55 }
56 int cmp_stu_by_name(const void* e1, const void* e2) {
57     // 比较名字就是比较字符串
58     // 字符串比较不能直接用><=来比较,应该用strcmp函数
59     return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
60 }
61 void test_struct() {
62     struct Stu s[3] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 10} };
63     int sz = sizeof(s) / sizeof(s[0]);
64     bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
65     //bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
66 }
67 
68 int main() {
69     test_int();
70     test_float();
71     test_struct();
72     return 0;
73 }

调试结果:

 

 以上知识点来自:https://www.bilibili.com/video/BV1q54y1q79w?p=38

posted @ 2022-06-01 14:16  ttweixiao9999  阅读(221)  评论(0编辑  收藏  举报