1. 程式人生 > 其它 >C 語言實現任意型別氣泡排序(C achieve bubble sort of any type)

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