前言
面試中經常遇到寫個排序算法,相信大家都會寫冒泡排序算法,雖然很多人會寫,我敢肯定,不是所有人都深知這個算法深層次的邏輯和規律,因爲對於一個普通的開發者來說,通常工作中絕大部分時間用不到這些內容,爲了應付面試,臨時把這個算法背下來,一般面試官一看寫對了,也不會再問什麼,如果想要研究下算法問題,我們就有必要徹底弄清楚其中的奧祕,這些是基礎,
一、概要
它重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果順序(如從大到小、首字母從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;
}
}
增加交換標誌,可以結束循環, 內循環如果沒有交換,說明已經是有序數列,直接跳出大循環,提高了效率。