冒泡排序大家都很熟悉,但优化到极致的冒泡算法,大家知道吗?今天我们介绍的算法,就是根据冒泡算法优化而来的,鸡尾酒排序(cocktail sort)。
根据冒泡排序的思想,我们不难得出,每次都会有最小或者最大(取决于升序还是降序)的数字放到数组最后的位置,倘若本轮循环并未发生交换,则说明数组已经有序。
同时,处于发生交换的位置之后的数据,已经是有序的状态。
根据这两点,我们做两个优化:
(1)设定一个bool变量,用来记录本轮循环是否发生交换,如果没有,可以直接break掉大循环。
(2)设定一个int变量,用来记录最后一次发生交换的位置,下次循环只需要循环到这个位置,就可以结束。
先写一个swap函数用以交换两个变量:
void swap(int *a, int *b)
{
if (*a == *b)
return;
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
注意:这里if一定要写上,否则如果a和b指向的地址相同,则不能正常完成交换(也可以这么想,相等的情况下,又何必有下面交换的过程呢?)
根据上面优化的思路,我们得到了第一个冒泡排序优化的版本:
void bubble_sort(int* a, int n)
{
int nExchangeIndex = n - 1;
for (int i = 0; i < n; ++i)
{
bool boMark = false;
int nSortBroad = nExchangeIndex;
for (int j = 0; j < nSortBroad; ++j)
{
if (a[j] > a[j + 1])
{
swap(&a[j], &a[j + 1]);
boMark = true;
nExchangeIndex = j;
}
}
if (!boMark)
break;
}
}
做到这里,其实冒泡排序已经是很完善了,但是还有一种特殊情况,比如待排序序列为:{1,2,3,4,5,6,7,8,9,0}。
这种情况下,显然大部分序列已经有序,唯独一个0,打破了沉寂,非要战战兢兢地跑完所有循环,才能排序成功,那这种情况,我们又该如何应对呢?
这就引出了我们的重头戏,鸡尾酒排序。
鸡尾酒排序实际就是在冒泡排序上优化而来,一次大循环内,写两个小循环,第一个从头到尾冒一遍,第二个从尾到头冒一遍,如此一轮大循环后,刚刚的例子就已经有序,在第二轮大循环过后,循环将会因为没有交换而break。
废话不多讲,直接上代码:
void cocktail_sort(int* a, int n)
{
int nMidLen = n >> 1; //由于大循环内会顺逆两次小循环,所以大循环只需要循环到数组个数的一半
int nExchangeIndex1 = n - 1;
int nExchangeIndex2 = 0;
for (int i = 0; i < nMidLen; ++i)
{
bool boMark = false;
int j;
int nSortBroad = nExchangeIndex1;
for (j = nExchangeIndex2; j < nSortBroad; ++j)
{
if (a[j] > a[j + 1])
{
swap(a + j, a + j + 1);
boMark = true;
nExchangeIndex1 = j;
}
}
if (!boMark)
break;
nSortBroad = nExchangeIndex2;
for (j = nExchangeIndex1; j > nSortBroad; --j)
{
if (a[j - 1] > a[j])
{
swap(a + j - 1, a + j);
boMark = true;
nExchangeIndex2 = j;
}
}
if (!boMark)
break;
}
}
注意,我们这鸡尾酒排序依旧可以使用刚刚的两个优化技巧,记录是否交换,记录最后交换的位置。
这个,我愿称之为冒泡排序的究极体,虽然他的时间复杂度并没有变化,但实际运用起来,基本都会比原始的冒泡排序快上一些。
曾经作为面试官考过这个题目,发现有大部分的都并不知道冒泡排序能进行优化,故拿出来分享一下,大家一起学习!!!