編碼 · 演算法
泡沫排序法教學:原理、程式碼與時間複雜度
簡答
泡沫排序法是一種簡單的排序演算法:反覆走訪串列,並交換每一對順序錯誤的相鄰元素。每一回合結束後,剩餘元素中最大的那個會移動(像氣泡般浮到)串列尾端的最終位置。它在平均與最差情況下需要 O(n²) 時間,對已排序的串列則是 O(n);額外空間為 O(1),並且是穩定排序。它主要用於教學;實際程式會使用內建的排序函式。
開啟行排序用白話說明原理
比較前兩個元素。如果第一個比較大,就交換它們。接著比較第二個和第三個,再比較第三個和第四個,依此類推直到串列結尾。這就是一回合(pass)。第一回合結束後,最大的元素必定在最後,因為每次比較都把它往右帶了一步。
對尚未排序的部分重複進行這一回合,未排序的部分每次會少一個元素。如果整個回合都沒有發生交換,串列就已排序完成,可以提早結束。
逐回合範例
將 [5, 1, 4, 2, 8] 由小到大排序。直線之後的元素已在最終位置。
| 回合 | 比較與交換 | 該回合後的串列 |
|---|---|---|
| 1 | 5↔1 交換、5↔4 交換、5↔2 交換、5 與 8 不動 | [1, 4, 2, 5, 8] (8 就定位) |
| 2 | 1 與 4 不動、4↔2 交換、4 與 5 不動 | [1, 2, 4, 5, 8] (5 就定位) |
| 3 | 1 與 2 不動、2 與 4 不動,沒有交換 | [1, 2, 4, 5, 8] 已排序,停止 |
沒有提早結束的機制時,演算法會再跑第四回合;有的話,這裡三回合就夠了。
Python 實作
def bubble_sort(items):
"""Sort a list in place, ascending. Returns the same list."""
n = len(items)
for end in range(n - 1, 0, -1): # last index of the unsorted part
swapped = False
for i in range(end):
if items[i] > items[i + 1]: # strict >, keeps equal items in order
items[i], items[i + 1] = items[i + 1], items[i]
swapped = True
if not swapped: # no swaps: already sorted
break
return items
print(bubble_sort([5, 1, 4, 2, 8])) # [1, 2, 4, 5, 8]
print(bubble_sort([3, 3, 1])) # [1, 3, 3]
print(bubble_sort([])) # []內層迴圈在 end 就停止,因為其後的元素都已就定位。swapped 旗標讓已排序的輸入有 O(n) 的最佳情況。
時間與空間複雜度
| 情況 | 比較次數 | 時間 | 原因 |
|---|---|---|---|
| 最佳(已排序) | n − 1 | O(n) | 一回合沒有交換,然後停止 |
| 平均(隨機順序) | 約 n²/2 | O(n²) | 平均約 n²/4 次交換 |
| 最差(反向排列) | n(n − 1)/2 | O(n²) | 每次比較都要交換 |
| 額外空間 | O(1) | 原地排序,只用幾個變數 |
對 10,000 個反向排列的元素,這是將近 5,000 萬次比較與交換。交換次數永遠等於逆序對(順序顛倒的元素對)的數量。
泡沫排序與其他排序法的比較
| 演算法 | 最佳 | 平均 | 最差 | 額外空間 | 穩定 |
|---|---|---|---|---|---|
| 泡沫排序 | O(n) | O(n²) | O(n²) | O(1) | 是 |
| 選擇排序 | O(n²) | O(n²) | O(n²) | O(1) | 否 |
| 插入排序 | O(n) | O(n²) | O(n²) | O(1) | 是 |
| 合併排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 是 |
| 快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | 否 |
| 堆積排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 否 |
Timsort(Python 的 sorted) | O(n) | O(n log n) | O(n log n) | O(n) | 是 |
泡沫排序是穩定的,因為它只交換相鄰元素,而且只在嚴格逆序時才交換,所以相等的元素不會互相越過。插入排序的複雜度與穩定性相同,但實際上寫入次數較少、速度較快,因此 Timsort 這類混合演算法內部用的是插入排序,而不是泡沫排序。
變體與常見錯誤
- 雞尾酒排序(cocktail shaker sort)在交替的回合中先由左到右、再由右到左,能讓留在尾端的小元素(稱為烏龜)更快移到前面。複雜度仍是 O(n²)。
- 梳排序(comb sort)比較相隔一段間隙的元素並逐步縮小間隙,能提早消除許多烏龜;間隙為 1 時就成了泡沫排序。
- 奇偶排序(odd-even transposition sort)先比較所有奇數對,再比較所有偶數對,常用來教授平行排序。
何時使用泡沫排序
在正式環境的程式碼中幾乎不會用。在 Python 中請呼叫 sorted(items) 或 items.sort(),它們使用 Timsort,時間為 O(n log n) 且穩定。不過泡沫排序仍值得了解:它是最容易解釋的排序法,正確性證明也容易理解,而且是考試或面試中關於迴圈、交換與複雜度的常見入門題。
唯一的實際優勢,是能在單次 O(n) 的走訪中偵測串列是否已排序,但只比較相鄰元素的單純迴圈就能做到,根本不必排序。
常見問題
泡沫排序是穩定的嗎?
是的。它只在左邊的元素嚴格大於右邊時才交換相鄰元素,所以相等的元素會保持原本的順序。
泡沫排序的時間複雜度是多少?
平均與最差情況是 O(n²)。若有「一回合沒有交換就提早結束」的機制,對已排序的輸入,最佳情況是 O(n)。
為什麼叫泡沫排序?
大的元素一次交換一步地往串列尾端移動,就像氣泡浮向水面。第 k 回合之後,最大的 k 個元素都已就定位。
泡沫排序比插入排序快嗎?
不快。兩者都是 O(n²),但插入排序通常操作次數較少,實際跑起來較快,尤其在幾乎已排序的資料上。
泡沫排序會用到額外記憶體嗎?
不會。它是原地排序,只需要 O(1) 的額外空間,用於迴圈索引和交換時的暫存值。