基礎排序:O(n²) 排序演算法#
基礎排序演算法時間複雜度為 O(n²),適用於小規模資料。雖然效率不高,但實作簡單,是學習排序的起點。
為什麼要排序?#
排序的價值不在排序本身,而在於「讓未來的搜尋變便宜」——它其實是一種對未來查詢的預防性投資。判準很簡單:一筆資料若會被反覆搜尋,且預先排序的成本低於每次線性掃描的累積成本,才值得排;只查一次的東西,排序純粹是浪費。
換句話說:排序一個你永遠不會搜尋的東西是徹底浪費;搜尋一個你從未排序的東西,只是稍微低效。 一旦資料有序,二分查找就讓 O(log n) 的查詢成為可能,這正是排序前置成本要換回的東西。
不要只盯著排序這一次的開銷:排序的成本會攤提到未來每一次查詢上。查得越多次,當初那一次排序就越划算。
核心概念#
有序度與逆序度#
- 有序度:陣列中有序元素對的數量,滿足
a[i] <= a[j]且i < j - 滿有序度:完全有序時的有序度 =
n*(n-1)/2 - 逆序度 = 滿有序度 - 有序度
排序的本質就是增加有序度、減少逆序度,直到達到滿有序度。
冒泡排序 (Bubble Sort)#
每次比較相鄰元素,將較大者「冒泡」到右側。
| 特性 | 說明 |
|---|---|
| 時間複雜度 | 最好 O(n)、最壞/平均 O(n²) |
| 空間複雜度 | O(1),原地排序 |
| 穩定性 | 穩定 |
冒泡排序程式碼
fun bubbleSort(arr: IntArray): IntArray {
val n = arr.size
for (i in 0 until n) {
var swapped = false
for (j in 0 until n - i - 1) {
if (arr[j] > arr[j + 1]) {
val tmp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = tmp
swapped = true
}
}
if (!swapped) break // 優化:無交換代表已有序
}
return arr
}插入排序 (Insertion Sort)#
將陣列分為「已排序區」與「未排序區」,逐一將未排序元素插入正確位置。
| 特性 | 說明 |
|---|---|
| 時間複雜度 | 最好 O(n)、最壞/平均 O(n²) |
| 空間複雜度 | O(1),原地排序 |
| 穩定性 | 穩定 |
插入排序程式碼
fun insertionSort(arr: IntArray): IntArray {
for (i in 1 until arr.size) {
val key = arr[i]
var j = i - 1
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j]
j--
}
arr[j + 1] = key
}
return arr
}為何插入排序優於冒泡排序?
兩者時間複雜度相同,但冒泡的「交換」需要 3 次賦值,插入的「移動」只需 1 次。實測顯示,插入排序比冒泡排序快約 3-7 倍。
選擇排序 (Selection Sort)#
每次從未排序區選出最小元素,放到已排序區末尾。
| 特性 | 說明 |
|---|---|
| 時間複雜度 | 最好/最壞/平均皆為 O(n²) |
| 空間複雜度 | O(1),原地排序 |
| 穩定性 | 不穩定 |
選擇排序程式碼
fun selectionSort(arr: IntArray): IntArray {
val n = arr.size
for (i in 0 until n) {
var minIdx = i
for (j in i + 1 until n) {
if (arr[j] < arr[minIdx]) {
minIdx = j
}
}
val tmp = arr[i]
arr[i] = arr[minIdx]
arr[minIdx] = tmp
}
return arr
}選擇排序是不穩定的!例如
[5, 8, 5, 2, 9],第一次選擇最小值 2 與第一個 5 交換後,兩個 5 的相對順序改變了。
三種基礎排序比較#
| 演算法 | 最好 | 最壞/平均 | 空間 | 穩定性 | 一句話 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n) | O(n²) | O(1) | 穩定 | 交換成本高,實務幾乎不用 |
| 插入排序 | O(n) | O(n²) | O(1) | 穩定 | 近乎有序時最快,小區間首選 |
| 選擇排序 | O(n²) | O(n²) | O(1) | 不穩定 | 交換次數最少,但無法提早結束 |
實務應用建議#
- 小規模資料:插入排序是首選
- 幾乎有序的資料:插入排序效率接近
O(n) - 工業級排序函式:當區間元素 <= 4 時,通常切換為插入排序