写在前面:
排序又称为分类,它是数据处理中经常用到的一种重要运算。
虽然并未列入世界最伟大的几大算法之一,但毫无疑问,在各行各业的各个时期排序都是作为奠基者般的存在为程序所调用,也为编程者所敬仰。只是,也许正是因为它与我们息息相关,以至于我们竟然时常忽略它的存在。
事实上我们生活中无时无刻不在做排序:考试成绩排名,按身高、年龄、能力高低去评判他人,划分任务处理的优先级,等等·······
前面2篇博客我们讨论了简单插入排序和简单选择排序算法,而今天我们进入“冒泡排序”的学习,一步步去探索排序算法如何在反思中前进,在山重水复处柳暗花明。
所谓冒泡排序,还是非常容易理解的,就是在每一次排序过程中,我们让最大的(最小的)那个元素逐渐冒泡到最后的(最前的)位置。关键是,该如何实现呢?这样做又有什么好处呢?
冒泡排序的基本思想是(以最大冒泡为例):针对{R1,R2,…Rn}的无序数据元素集合,从R1开始,两两比较Ri和R(i+1),若前者大于后者,则交换两者数据元素(即互换位置)。第一次全部比较完之后,Rn为序列中最大的数据元素。以此类推,第二次比较完后,R(n-1)是除Rn之外最大的数据元素。
但是有一点需要注意,就是在冒泡排序算法中,我们有必要全部都比较完吗(最坏情况下确实需要如此)?
不一定,如果经历了第i趟排序后,整个序列已然有序,那后面岂不都做了无用功!为了避免浪费资源,我们立下flag(当然不是那个flag啦),用来标志某一趟比较是否空跑(即只比较不移动)。这一点将在下面的算法代码描述中加以体现。
那么现在我们用C语言来明确描述一下我们的冒泡排序算法(以double型数据为例):
void bubblesort(double a[],int n) {
int i,j,flag = 1; //flag is used to label if the sequence is already ordered
double swap;
for (i = 0;i < n-1 && flag == 1;i++) { //Two conditions
flag = 0; //Set flag in every i loop
for (j = 0;j < n-i-1;j++) { //Note it cannot be written as "j < n-i" for the reason that a[n] does not exsit
if (a[j] > a[j+1]) { //swap data
swap = a[j+1];
a[j+1] = a[j];
a[j] = swap;
flag = 1;
}
}
if (flag == 0) //if ordered,then return
return;
}
}
代码的思想还是比较清晰的,只是i,j的边界的处理要非常小心,因为很容易出现数组访问越界的情况。
为证明其可用性,截图如下以证清白:
接下来,我们感兴趣的地方是,看来简单选择、简单插入、冒泡排序都可以实现排序,那么它们谁的效率更高,性能更优呢?
其实这是个仁者见仁智者见智的问题(这不相当于没说么。。。),我们在同等条件下,跑三个大型范例(用随机数),测试其花费时间的多少:
数据量10000时:
数据量20000时:
数据量40000时:
是不是很震撼!
是不是很遗憾?
咱们都琢磨到第三篇了,怎么这个冒泡排序算法还是这么慢?
诸位莫急。其实直观来讲,冒泡排序算法比较慢是非常正常的,因为它很可能伴随着大量的数据交换。但是问题来了:性能最佳的插入排序还可能伴随着大量的数据移动呢!
但是想想这么个道理:移动数据一条指令足以,但是交换数据可是要三条指令啊。加上冒泡排序里有个时而鸡肋的flag,平均效率低于插入排序实在是太正常了。
那么冒泡排序就没有有点了么?当然不是!当元素序列一开始已经非常有序时,它的速度就会非常快!基本上空跑一趟flag知道白跑了,马上就可以返回。在某些场合冒泡排序还是有它的优势的,然而,我并没看到谁会主张用冒泡排序。。。
因为下一篇,我们会引入一个排序界的小天王——快速排序。
快排,听名字就知道实力很强!
那么关于冒泡排序的分享,就到此为止,下次再会啦!