前言
面试中经常遇到写个排序算法,相信大家都会写冒泡排序算法,虽然很多人会写,我敢肯定,不是所有人都深知这个算法深层次的逻辑和规律,因为对于一个普通的开发者来说,通常工作中绝大部分时间用不到这些内容,为了应付面试,临时把这个算法背下来,一般面试官一看写对了,也不会再问什么,如果想要研究下算法问题,我们就有必要彻底弄清楚其中的奥秘,这些是基础,
一、概要
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
简单讲就是:将一组大小的杂乱的元素,逐个比较两个相邻的元素,较大或较小的放在右边,最后达到升序或降序的排序
二、基本原理
-
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
三、算法分析
算法复杂度:N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次
最好时间复杂度为O(N)
Cmax = N(N-1)/2 = O(N2)
Mmax = 3N(N-1)/2 = O(N2)
冒泡排序的最坏时间复杂度为O(N2)
冒泡排序的平均时间复杂度为O(N2)
算法稳定性 : 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以, 如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候 也 不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法
四、代码实现
以java代码为例
public static void bubbleSort2(int[] list) {
for(int i = 0 ; i< list.length-1 ; i++) {
for(int j= 0 ; j < list.length-1-i ; j++) {
System.out.println("==========外循环 i====" + i + "==========内循环 j====" + j);
System.out.println(" 换位前: " + Arrays.toString(list));
System.out.println(" 比较的元素为:第" + j + " 和 " +(j+1) );
if(list[j]>list[j+1]) {
int temp = list[j];
list[j]=list[j+1];
list[j+1]=temp;
}
System.out.println(" 换位后: " + Arrays.toString(list) );
}
}
}
打印日志为:
==========外循环 i====0==========内循环 j====0
换位前: [9, 2, 5, 1]
比较的元素为:第0 和 1
换位后: [2, 9, 5, 1]
==========外循环 i====0==========内循环 j====1
换位前: [2, 9, 5, 1]
比较的元素为:第1 和 2
换位后: [2, 5, 9, 1]
==========外循环 i====0==========内循环 j====2
换位前: [2, 5, 9, 1]
比较的元素为:第2 和 3
换位后: [2, 5, 1, 9]
==========外循环 i====1==========内循环 j====0
换位前: [2, 5, 1, 9]
比较的元素为:第0 和 1
换位后: [2, 5, 1, 9]
==========外循环 i====1==========内循环 j====1
换位前: [2, 5, 1, 9]
比较的元素为:第1 和 2
换位后: [2, 1, 5, 9]
==========外循环 i====2==========内循环 j====0
换位前: [2, 1, 5, 9]
比较的元素为:第0 和 1
换位后: [1, 2, 5, 9]
分析上述代码可以看出,
总共两层循环,内层循环次数随着外层次数的增加而减少,为什么要这样设计呢?
原因分析:
根据算法规律,每次排序都能把数组内的最大或最小元素放在数组的最顶端,以上述代码为例,上述是以从小到大排序,第一次外循环结束就能把最大的放在最右边,所以在内循环中可以看到,互相比较元素的下标随着外循环的增加而增加,比较的次数也就越来越小,最后直接完排序,
分析日志可以看出
第一次外循环结束,已经把最大的元素推到最右边了,后续的比较,已经不再不需要这个元素参与了
总结:如果希望元素从小到大排序,第一次外循环结束就能把最大元素放至最右边,第二次外循环线束就能把第二大的元素放至右二的位置,以此内推,直至排序结束
五、算法优化
具体代码如下
public static void bubbleSort2(int[] list) {
boolean bChange = false; // 交换标志
for(int i = 0 ; i< list.length-1 ; i++) {
bChange = false;
for(int j= 0 ; j < list.length-1-i ; j++) {
System.out.println("==========外循环 i====" + i + "==========内循环 j====" + j);
System.out.println(" 换位前: " + Arrays.toString(list));
System.out.println(" 比较的元素为:第" + j + " 和 " +(j+1) );
if(list[j]>list[j+1]) {
int temp = list[j];
list[j]=list[j+1];
list[j+1]=temp;
bChange = true;
}
System.out.println(" 换位后: " + Arrays.toString(list) );
}
// 如果标志为false,说明本轮遍历没有交换,已经是有序数列,可以结束排序
if (false == bChange)
break;
}
}
增加交换标志,可以结束循环, 内循环如果没有交换,说明已经是有序数列,直接跳出大循环,提高了效率。