C語言是簡潔的強大的,固然也有不少坑。C語言也是有點業界良心的,至少它實現了2個最最經常使用的算法:快速排序和二分查找。算法
咱們知道,對於C語言標準庫 qsort和 bsearch:數組
a. 它是「泛型」的,能夠對任何類型進行排序或二分。函數
b. 咱們使用時必須自定義一個比較函數看成函數指針傳入。學習
c語言要實現泛型,基本上就只有 void指針提供的弱爆了的泛型機制,容易出錯。優化
這篇文章中,我實現了 標準庫qsort和bsearch函數,最基本的正確性和泛型固然要保證了。spa
在這裏,不涉及優化(寫標準庫實現的那幫人巴不得用匯編實現),只展示算法的運行原理和泛型的實現機制。指針
1.C語言標準庫qsort源碼實現。我先呈上完整實現,而後具體剖析。code
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string.h> void swap(const void* a, const void* b, int size) { assert(a != NULL && b != NULL); char tmp = 0; int i = 0; while (size > 0) { tmp = *((char*)a + i); *((char*)a + i) = *((char*)b + i); *((char*)b + i) = tmp; ++i; --size; } } void Qsort(void* base, int left, int right, int size, int (*cmp)(const void* a, const void* b)) { assert(base != NULL && size >= 1 && cmp != NULL); /* left may be < 0 because of the last - 1 */ if (left >= right) return; char* pleft = (char*)base + left * size; char* pkey = (char*)base + (left + (right - left) / 2) * size; swap(pleft, pkey, size); int last = left; char* plast = (char*)base + last * size; for (int i = left + 1; i <= right; ++i) { char* pi = (char*)base + i * size; if (cmp(pi, pleft) < 0) { ++last; plast = (char*)base + last * size; swap(pi, plast, size); } } swap(pleft, plast, size); Qsort(base, left, last - 1, size, cmp); Qsort(base, last + 1, right, size, cmp); } int cmp_string(const void* a, const void* b) { assert(a != NULL && b != NULL); const char** lhs = (const char**)a; const char** rhs = (const char**)b; return strcmp(*lhs, *rhs); } int cmp_int(const void* a, const void* b) { assert(a != NULL && b != NULL); const int* lhs = (const int*)a; const int* rhs = (const int*)b; if (*lhs < *rhs) { return -1; } else if (*lhs == *rhs) { return 0; } else { return 1; } } int main(int argc, char* argv[]) { int a[] = {-2, 0, 5, 1, 10, 8, 5, 4, 3, 9}; int len1 = sizeof(a) / sizeof(a[0]); fprintf(stdout, "before sort:\n"); for (int i = 0; i < len1; ++i) { fprintf(stdout, "%d ", a[i]); } fprintf(stdout, "\n"); Qsort(a, 0, len1 - 1, sizeof(a[0]), cmp_int); fprintf(stdout, "after sort:\n"); for (int i = 0; i < len1; ++i) { fprintf(stdout, "%d ", a[i]); } fprintf(stdout, "\n"); const char* b[] = {"what", "chenwei", "skyline", "wel", "dmr"}; int len2 = sizeof(b) / sizeof(b[0]); fprintf(stdout, "before sort:\n"); for (int i = 0; i < len2; ++i) { fprintf(stdout, "%s-->", b[i]); } fprintf(stdout, "\n"); Qsort(b, 0, len2 - 1, sizeof(b[0]), cmp_string); fprintf(stdout, "after sort:\n"); for (int i = 0; i < len2; ++i) { fprintf(stdout, "%s-->", b[i]); } fprintf(stdout, "\n"); }
qsort基本實如今K&R裏有很是詳細的描述,我這裏重點解釋的是 swap函數,這個函數是實現泛型的基石。blog
首先對qsort函數原型說明幾點:排序
a.Qsort函數原型裏面沒有標準庫qsort的 len, 而是使用 left 和 right 來指明每次待排序的區間,用兩個值來表示一個區間很是直觀且優雅。
b.對於數據類型大小,我沒有使用 size_t 無符號類型。
C語言中無符號類型雖然能夠對數組提供負向越界保證和2倍空間,可是因爲坑爹的類型提高規則滋生了N多的bug,我是儘可能少用這個。
而後就是 swap函數.
首先咱們知道 數據元素都是以 比特位 形式暫存在 內存中的,後簡化爲以 字節 形式暫存在內存中。
咱們日常的一個 swap 函數,若是交換的數據是 int 型, 咱們就是:
void swap(int* a , int* b) { int tmp = *a; *a = *b; *b = tmp; }
如今咱們不知道元素是什麼類型,那咱們怎麼作到泛型呢? 首先 傳入的兩個指針確定是 void 指針,如何肯定 void所指元素類型呢? 其實咱們這裏是不須要知道 元素類型的。
咱們這裏的目的是什麼? 咱們就是要 交換兩個元素的 字節序列,咱們每次交換一個字節,直到交換完爲止,這樣就達到目的了,這時咱們就須要第三個參數:待排元素的所佔字節數。
咱們知道C語言中結構體是能夠直接複製的,好比:
struct test { int a; char b; }; struct test a, b; a = b;
C語言是怎樣支持這種直接複製呢? 其實最終就是 兩個元素的字節序列的複製。注意到,若是struct裏面有指針,那麼這個結構體是引用語義的,兩個元素的指針成員指向同一內存。
咱們這裏的swap函數其實就是 手工模擬了 相似結構體複製 的整個過程。咱們每次交換一個 字節,總共交換的次數就是 元素的sizeof大小。
注意兩點:
a. 數據的字節序列是有 大端小端之分的,個人這個實現是不能 跨大端小端的。若是排序過程都始終都在同一機器中,那麼無需擔憂。
b. 數據的字節序列多是有 內存對齊的,主要是在結構體之中。 因此個人這個實現受 機器 內存對齊規則的影響,可是 排序發生在同一機器中的話,這個是不會影響正確性的。
總而言之,代碼實現受機器底層 數據大小端表示和 內存對齊策略 的影響。可是實際上 一個排序過程是不可能 一部分進行在這臺機,另外一部分在另一臺機器上進行的(僅考慮單機),因此個人這個實現是能夠很好工做的。
Qsort的快排思想就很簡單了,咱們最須要注意的就是 每次對 交換元素的首字節地址進行更新,咱們都是經由char*轉換,由於char*所指元素正好1字節,正好模擬每次一字節的swap.
2.C語言標準庫bsearch源碼實現
void* Bsearch(void* base, int len, int size, const void* key, int (*cmp)(const void* a, const void* b)) { assert(base != NULL && len >= 0 && size >= 1 && cmp != NULL); int low = 0; int high = len - 1; while (low <= high) { int mid = low + (high - low) / 2; char* pmid = (char*)base + mid * size; if (cmp(pmid, key) < 0) { low = mid + 1; } else if (cmp(pmid, key) > 0) { high = mid - 1; } else { return pmid; } } return NULL; } int cmp_int(const void* a, const void* b) { assert(a != NULL && b != NULL); const int* lhs = (const int*)a; const int* rhs = (const int*)b; if (*lhs < *rhs) { return -1; } else if (*lhs == *rhs) { return 0; } else { return 1; } } int cmp_string(const void* a, const void* b) { assert(a != NULL && b != NULL); const char** lhs = (const char**)a; const char** rhs = (const char**)b; return strcmp(*lhs, *rhs); } int main(int argc, char* argv[]) { int a[] = {-2, 0, 1, 3, 4, 5, 5, 8, 9, 10}; int len1 = sizeof(a) / sizeof(a[0]); int tmp = 5; int* res1 = (int*)Bsearch(a, len1, sizeof(a[0]), &tmp, cmp_int); if (res1 != NULL) { fprintf(stdout, "found it\n"); } else { fprintf(stdout, "Not found\n"); } const char* str[] = {"chenwei", "dmr", "skyline", "wel", "what"}; int len2 = sizeof(str) / sizeof(str[0]); const char* p = "chenwei"; char* res2 = (char*)Bsearch(str, len2, sizeof(str[0]), &p, cmp_string); if (res2 != NULL) { fprintf(stdout, "found it\n"); } else { fprintf(stdout, "Not found\n"); } return 0; }
歡迎你們批評指正,共同窗習。轉載請註明出處,謝謝。