# 泡沫排序法(Bubble Sort)
應該是大部分人第一次接觸到的排序法。
## 作法
以升序泡沫排序法來說:將相鄰的元素兩兩比對,若左邊的數比右邊的數大,則將兩數交換,若沒有則換下一個元素對比,做完一輪之後序列中最大的數就會被排到最後一個位置,就像泡泡一樣慢慢冒出來,第一個冒出來的就是最大的泡泡。
### 舉例說明
比如說我今天有一陣列,我想將此陣列中的數**由小到大**排序:9 4 2 6 3 7 5
我先來對比 9 和 4,9 > 4所以將9和4做交換,此時陣列為:4 9 2 6 3 7 5
![](https://i.imgur.com/iG93iYA.png)
接著對比 9 2,我們發現9 > 2所以將9和2交換,此時陣列為:4 2 9 6 3 7 5
![](https://i.imgur.com/zyys0Qz.png)
![](https://i.imgur.com/Xzxg8fm.png)
依此類推,後面我只將每次交換結果列出:
=> 4 2 6 9 3 7 5
![](https://i.imgur.com/WmlyNJ3.png)
=> 4 2 6 3 9 7 5
![](https://i.imgur.com/gV2hq85.png)
=> 4 2 6 3 7 9 5
![](https://i.imgur.com/nwkjQgJ.png)
=> 4 2 6 3 7 5 9
![](https://i.imgur.com/6tet1vz.png)
經過一輪排序後,此時結果為 4 2 6 3 7 5 9
但你會發現這並不是由小到大排序啊?
沒錯,當一輪結束後陣列沒有達到理想狀態時,我們還需要再繼續排序:
4 2 6 3 7 5 9
=> 2 4 6 3 7 5 9
=> 2 4 3 6 7 5 9
=> 2 4 3 6 5 7 9
再繼續下一輪
2 4 3 6 5 7 9
=> 2 3 4 6 5 7 9
=> 2 3 4 5 6 7 9
此時看起來排序已經結束了,但其實並沒有,每一輪排序我們會將一個數定下來,比如說第一輪我們會將 9 擺到最後因為它是陣列中最大的元素,第二輪是把 7 陣列中次大的元素定下來,第三輪則是把 6 陣列中第三大的元素定下來...以此類推。
由此我們可以得知,使用泡沫排序法一共需要進行 **n - 1** 輪(n為元素個數)。
### 優化
因為排序過程中,各元素不斷接近自己的位置,如果一趟比較下來沒有進行過交換就說明序列有序,就像我們前面的例子,其實在第三輪的時候就已經排序完畢,但是卻要進行六輪排序,因此要在此排序過程中設置一個標誌flag判斷元素是否進行過交換,從而減少不必要的比較。
### 規則小結
1. 一共進行 `陣列大小-1` 輪的比較
2. 每一趟排序的次數在逐漸減少
3. 如果我們發現在某趟排序中沒有發生一次交換,可以提前結束泡沫排序。
## 程式碼實現
```java=
public class BubbleSort {
public static void main(String[] args) {
int arr[] = {9,4,2,6,3,7,5};
System.out.println("原陣列: "+Arrays.toString(arr));
//泡沫排序,將最大的數排在最後
int temp = 0; //臨時變數
for(int j = 0; j < arr.length -1; j++) {
for(int i = 0; i < arr.length - 1 - j; i++) {
//如果當前的數比後一位的數大則交換
if(arr[i] > arr[i+1]) {
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
System.out.println("第 "+(j+1)+" 輪排序: "+Arrays.toString(arr));
}
}
}
```
output
```java=
原陣列: [9, 4, 2, 6, 3, 7, 5]
第 1 輪排序: [4, 2, 6, 3, 7, 5, 9]
第 2 輪排序: [2, 4, 3, 6, 5, 7, 9]
第 3 輪排序: [2, 3, 4, 5, 6, 7, 9]
第 4 輪排序: [2, 3, 4, 5, 6, 7, 9]
第 5 輪排序: [2, 3, 4, 5, 6, 7, 9]
第 6 輪排序: [2, 3, 4, 5, 6, 7, 9]
```
時間複雜度為 O($n^2$),因為是兩個for迴圈。
### 優化
從上面的輸出結果可以看到,第三輪到第六輪的結果都相同,所以我們需要進行優化,一旦排序完便不再繼續排序。
定義一個變數 flag 預設為 false
```java=
boolean flag = false; //標示變數,表示是否進行交換
```
一旦交換則將 flag設為true,若一輪排序後flag仍為false代表這一輪中都沒有交換過,即結束排序。
```java=
for(int j = 0; j < arr.length -1; j++) {
for(int i = 0; i < arr.length - 1 - j; i++) {
//如果當前的數比後一位的數大則交換
if(arr[i] > arr[i+1]) {
flag = true;
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
if(!flag) { //在一輪排序中一次交換都沒有發生過
break;
} else {
flag = false; //重置flag進行下一輪排序
}
System.out.println("第 "+(j+1)+" 輪排序: "+Arrays.toString(arr));
}
```
output
```java=
原陣列: [9, 4, 2, 6, 3, 7, 5]
第 1 輪排序: [4, 2, 6, 3, 7, 5, 9]
第 2 輪排序: [2, 4, 3, 6, 5, 7, 9]
第 3 輪排序: [2, 3, 4, 5, 6, 7, 9]
```
接著來測試一把若有八萬筆資料排序要花多少時間:
```java=
public static void main(String[] args) {
// int arr[] = {7,2,5,4,5,8,7};
// System.out.println("原陣列: "+Arrays.toString(arr));
int arr[] = new int[80000];
for(int i = 0; i < 80000; i++) {
arr[i] = (int)(Math.random() * 80000);
}
Instant startTime = Instant.now();
bubbleSort(arr);
Instant endTime = Instant.now();
System.out.println("共耗時:" + Duration.between(startTime, endTime).toMillis() + " 毫秒");
}
```
output
```java=
共耗時:7015 毫秒
共耗時:7012 毫秒
共耗時:7331 毫秒
共耗時:7062 毫秒
```
還滿穩定的,測了四次都是差不多 7 秒多。
## 時間複雜度
假設今天要對一陣列使用泡沫排序法由小到大排序。
### 最佳:O(n)
當資料順序恰好為由小到大時,第一輪排序未進行任何置換則提前結束。
### 平均:O(n^2^)
第 n 筆資料,平均需要比較 `(n-1)/2` 次
### 最差:O(n^2^)
當資料順序恰好為由大到小時,每輪分別執行:n-1、n-2、.. 、1次,`(n-1)+(n-2)+...+1 = n(n-1)/2` => O(n^2^)