一.冒泡排序原理
冒泡排序(Bubble Sort)应该是我们学习的第一个排序算法,因排序过程类似泡泡上浮或则下沉的形式所以叫冒泡排序。先看一个简单的实例整形数组排序:
int bubble_sort1(int* array, int nSize) { int nIndex1 = 0; int nIndex2 = 0; RET_VAL_IF_FAIL(array != NULL && nSize > 1, -1) for(nIndex1 = 0; nIndex1 < nSize - 1; nIndex1++) { for(nIndex2 = 1; nIndex2 < nSize - nIndex1; nIndex2++) { if(array[nIndex2] < array[nIndex2 - 1]) { int nTemp = array[nIndex2]; array[nIndex2] = array[nIndex2 - 1]; array[nIndex2 - 1] = nTemp; } } } return (0); }外层nIndex < nSize - 1,因为最后一次冒泡只有一个元素,故可以取消此次冒泡。关于函数里面的RET_VAL_IF_FAIL宏后面会提到。
二.冒泡排序改进
假设输入序列为:3,2,1,4,5,6.。可以得到第一次交换输出:2,3,1,4,5,6。第二次输出:2,1,3,4,5,6。此时nIndex2等于2,观察序列从3开始已经是有序了,第二次冒泡的时候只需要比较从0到nIndex2-1的位置即可。另外,当第二次冒泡的时候交换2和1的位置序列此时已经是升序,所以不需要再进行第三次冒泡。得到以下代码:
int bubble_sort2(int* array, int nSize) { int nIndex = 0; int flag = nSize; int key = 0; RET_VAL_IF_FAIL(array != NULL && nSize > 1, -1) while(flag > 1) { for(nIndex = 1; nIndex < flag; nIndex++) { if(array[nIndex1] < array[nIndex - 1]) { int nTemp = array[nIndex]; array[nIndex] = array[nIndex - 1]; array[nIndex - 1] = nTemp; key = nIndex; } } flag = key < flag-1 ? key : flag - 1; } return (0); }
上面的代码基于输入序列为整形,通用算法不应该局限在具体的数据类型,对于具体操作的类型我们应该交给调用者来决定。C语言常用回调函数来隔离这类变化。我们得到以下代码:
int bubble_sort3(void** ppvArray, size_t nCount, SortCompaerFunc CompareFunc) { int nKey = 0; int nIndex = 0; int nFlag = nCount; RET_VAL_IF_FAIL((ppvArray != NULL) && (CompareFunc != NULL), SORT_ERR_INVALID_PARAM); if(nCount < 2) { return (SORT_ERR_OK); } while(nFlag > 1) { for(nIndex = 1; nIndex < nFlag; nIndex++) { if(CompareFunc(ppvArray[nIndex], ppvArray[nIndex - 1]) < 0) { void* pvTemp = ppvArray[nIndex]; ppvArray[nIndex] = ppvArray[nIndex - 1]; ppvArray[nIndex - 1] = pvTemp; nKey = nIndex; } } nFlag = nKey < nFlag-1 ? nKey : nFlag - 1; } return (SORT_ERR_OK); }
排序算法有好几种,我们不可能为每个排序算法都各自写一个测试程序,当然也不是不可以,这里代码量并不多。更加提倡的做法是写一个小小的测试模块每种排序算法都通用。
定义错误码、比较回调函数原型、排序函数原型:
typedef int (*SortCompaerFunc)(void* pvParam1, void* pvParam2); typedef int (*Sort_Func)(void** pvData, size_t nCount, SortCompaerFunc CompareFunc); typedef enum tagSORT_ERR_E { SORT_ERR_OK, SORT_ERR_OOM, SORT_ERR_FAIL, SORT_ERR_INVALID_PARAM }SORT_ERR_E;
这里比较函数只需要两个参数就可以了,由于不知道具体参数类型就设置为void*类型。排序函数原型也设定为固定形式:输入序列、序列长度和比较回调函数,返回值表示排序是否成功。
测试模块函数:
static void** dump_creat_sort_array(size_t nCount) { int* pnNewArray = malloc(sizeof(int) * nCount); int nIndex = 0; for(nIndex = 0; nIndex < nCount; nIndex++) { pnNewArray[nIndex] = rand(); } printf("\n"); return ((void**)pnNewArray); } static void dump_sort_test(size_t nCount, Sort_Func Sortfunc, SortCompaerFunc CompareFunc) { int nIndex = 0; void **ppvArray = dump_creat_sort_array(nCount); Sortfunc(ppvArray, nCount, CompareFunc); printf("%d\n", nCount); for(nIndex = 1; nIndex < nCount; nIndex++) { assert(ppvArray[nIndex - 1] <= ppvArray[nIndex]); } printf("test ok\n"); free(ppvArray); ppvArray = NULL; } void dump_sort(int nTimes, Sort_Func Sortfunc, SortCompaerFunc CompareFunc) { int nIndex = 0; for(nIndex = 0; nIndex <= nTimes; nIndex++) { dump_sort_test(nIndex, Sortfunc, CompareFunc); } }
测试模块动态调用随机函数创建测试序列,这样可以保证测试样例足够均匀。如果我们想测试某个排序函数只需要把该函数传入测试模块,并提供比较回调函数和需要测试的数据长度即可。对于排序结果调用assert来判断前一个元素一定小于等于后一个元素(排序结果为升序)。
测试冒泡排序:
int sort_compare_int(void* pvParam1, void* pvParam2) { return ((int)pvParam1 > (int)pvParam2 ? 1 : -1); } int main(int argc, char** argv) { dump_sort(20, bubble_sort, sort_compare_int); return (0); }测试长度范围从0-20的随机序列。可以根据自己需要自由设定。
测试结果: