一.冒泡排序原理
冒泡排序(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的随机序列。可以根据自己需要自由设定。
测试结果: