昨天理解 Bubble Sort 怎麼運作之後,今天要試著把紙上理解的流程真正轉換成程式碼。
先簡單回顧 Bubble Sort 的三個重要概念:
假設今天有一組資料:
const arr = [5, 3, 8, 1];
昨天試著一步一步把它排好,但真的要轉換成程式碼時,我得先思考:要怎麼把 Compare、Swap、Pass 寫成程式?
回想昨天手算 Bubble Sort 的流程,我覺得程式至少需要處理三件事情:
帶著這三個問題,我開始嘗試把 Bubble Sort 寫成 TypeScript。
但真正開始實作之後,才發現還有一些原本沒有注意到的細節。
一開始處理 Swap 時,我直覺寫成:
arr[i] = arr[i + 1];
arr[i + 1] = arr[i];
但第一行執行後,原本 arr[i] 的值就已經被覆蓋,因此需要先用一個變數暫存:
temp = arr[i]; // 暫存左邊的數字
arr[i] = arr[i + 1]; // 將右邊的數字移到左邊
arr[i + 1] = temp; // 將暫存的數字移到右邊
這樣才能在不遺失原本數值的情況下完成交換。
解決 Swap 之後,我又陸續遇到幾個問題:
實作時,我先使用一個 for,確認第一輪的 Compare 與 Swap 是否符合預期,再來思考該如何進行下一輪。
第一輪跑完後:
[5, 3, 8, 1]
↓
[3, 5, 1, 8]
最大的 8 的確被推到最後面,代表第一輪的比較與交換方向是正確的。
但前面的 [3, 5, 1] 還沒有排序完成,接下來就需要讓相同的比較流程繼續進行第二輪、第三輪,因此使用雙重迴圈:
每完成一輪,最大的數字就會被推到目前比較範圍的最後面,因此下一輪可以縮小比較範圍,不需要再次比較已經排好的部分。
整理前面的問題後,最後寫出的版本:
const arr = [5, 3, 8, 1];
let temp = 0;
for (let j = arr.length; j > 1; j--) {
console.log("目前 Pass 的比較範圍", j);
for (let i = 0; i < j - 1; i++) {
if (arr[i] > arr[i + 1]) {
temp = arr[i]; // 暫存左邊的數字
arr[i] = arr[i + 1]; // 將右邊的數字移到左邊
arr[i + 1] = temp; // 將暫存的數字移到右邊
console.log("[如果左邊的數字比較大,就交換]", arr);
} else {
console.log("[不需要交換]", arr);
}
}
}
透過 console 可以看到每一輪實際發生的事情:
目前 Pass 的比較範圍 4 // 第一輪
[如果左邊的數字比較大,就交換] [3, 5, 8, 1] // 5、3 交換
[不需要交換] [3, 5, 8, 1] // 5、8 不需要交換
[如果左邊的數字比較大,就交換] [3, 5, 1, 8] // 8、1 交換
目前 Pass 的比較範圍 3 // 第二輪
[不需要交換] [3, 5, 1, 8] // 3、5 不需要交換
[如果左邊的數字比較大,就交換] [3, 1, 5, 8] // 5、1 交換
目前 Pass 的比較範圍 2 // 第三輪
[如果左邊的數字比較大,就交換] [1, 3, 5, 8] // 3、1 交換
最後成功得到:
[1, 3, 5, 8]
這次實作其中一個收穫,是開始思考:當只剩下一個元素時,還有需要再跑一輪嗎?
實際測試後發現,就算多跑一輪也不會影響最後的排序結果,但這一輪其實沒有進行任何比較,所以是沒有必要的。
另外,透過實際寫程式碼,也讓我更了解兩層迴圈各自在做什麼。內層迴圈負責一次 Pass 裡的比較與交換;外層迴圈則負責讓 Pass 繼續進行,而且每完成一輪,就可以縮小下一輪需要比較的範圍。
原本覺得很直覺的「比較、交換、再跑下一輪」,真正轉換成程式碼之後,才發現每一個步驟都需要轉換成明確的程式條件。
這邊也請 AI 做了一個小工具,可以實際操作 Bubble Sort 每一步「比較」與「交換」的過程,幫助自己更熟悉整個排序流程。
https://chatgpt.com/s/w_6aa27bac36448191811e265c828916ab
文章完成後,我也找了一些其他 Bubble Sort 的實作方式,看看不同的寫法:
iT 邦幫忙,〈Bubble Sort 相關實作〉
https://ithelp.ithome.com.tw/articles/10223288
PJCHENder,〈[演算法] 氣泡排序法(Bubble Sort)〉
https://pjchender.blogspot.com/2017/09/bubble-sort.html