一、写在前面的话
今天在地铁上看到有博主分析时间复杂度,提到冒泡排序的时间复杂度是O(n²) 。我竟然想起这么多年没有再写过冒泡了,于是乎自己再动手整理一遍算是复习也是新的学习。
二、冒泡的思想
冒泡的思想其实就是两两比较,每趟都能确定一个最大/最小的数值在最末尾。
那举例来说明,例:我们要将数组int[] a = {7,6,5,4,3,2,1}做正序排序。这是一个比较极端的情况。我们来分析一下冒泡的思想。
它会怎么做?
1.a[0]和a[1]比较,a[0]>a[1] == true ? a[0] 交换至a[1]的位置 : a[0]原封不动
2.a[1]和a[2]再做相同操作,直到a[a.length-2]和a[length-1]比较完毕。
故此,我们的结果是
/**
* 6,5,4,3,2,1,7
* 5,4,3,2,1,6,7
* 4,3,2,1,5,6,7
* 3,2,1,4,5,6,7
* 2,1,3,4,5,6,7
* 1,2,3,4,5,6,7
*/
三、码上见
按照刚才的思想,我们编写如下代码:
Integer[] a = new Integer[] {7,6,5,4,3,2,1};
int temp = 0;
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a.length-1; j++) {
if(a[j] > a[j+1]) {
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
/*------------------------- 排序后的结果----------------------*/
1 2 3 4 5 6 7
大家有没有注意到,按照冒泡的思想我们只需要走a.length-1次就可以完成整个操作 ,而上面的代码走了a.length次。
另外,每次都可以在尾部确立一个最大数,那除去第一次比较其他次数有必要再和最大值作比较吗?我们可以减掉外层循环的变量刚好规避掉这个问题。所以我们的排序可以变为:
/**
* 6,5,4,3,2,1, 7 确立了7是最大下次7不参与 排序完i=1
* 5,4,3,2,1, 6,7 确立了6是最大,但该趟排序的6没有与7比较,因为内循环减掉了i
* 4,3,2,1, 5,6,7 同上,5不与6比较...
* 3,2,1, 4,5,6,7
* 2,1, 3,4,5,6,7
* 1, 2,3,4,5,6,7 最后一趟第一位一定是最小的数值了
因为大家都两两比较过
就算最极端的情况最小的数在最末尾
它也被带到了a[0]的位置上。故此可以不用比较了
*/
代码变为:
Integer[] a = new Integer[] {7,6,5,4,3,2,1};
int temp = 0;
for (int i = 0; i < a.length-1; i++) { // 第一位不参与比较可以减少一次
for (int j = 0; j < a.length-1-i; j++) { //最大位数不参与二次比较 减掉i
if(a[j] > a[j+1]) {
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
ok,优化完毕了。目前还有一个问题没有考虑,我们是用最极端的情况,那有没有最优情况呢?
比如我们需要排序的数组为:int[] a = {1,2,3,4,5,6,7}
相比较上面的情况有什么区别呢?对喽,就是我们第一趟比较的时候发现我们if的条件没有一个成立的
此时我们根据发现再来优化。
Integer[] a = new Integer[] {1,2,3,4,5,6,7};
int temp = 0;
boolean flag = false;//设立flag检测
for (int i = 0; i < a.length-1; i++) { // 第一位不参与比较可以减少一次
for (int j = 0; j < a.length-1-i; j++) { //最大位数不参与二次比较 减掉i
if(a[j] > a[j+1]) {
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
//改变flag
flag = true;
}
}
//当第一趟比较完我们来检查flag的值
if(!flag)
break;//直接跳出,说明是完全有序的状态
}
OK。讲完了。
四、总结
现在来想想地铁上看到的博主分享的冒泡排序的时间复杂度。没错,一般情况下他的时间复杂度就是O(N²)。那如果是最优情况的话只需要比较n-1替换0,所以时间复杂度可以是O(n)。
OK,以上。有不足之处欢迎指正共同学习进步!