(Day 13) 選擇排序 (Selection Sort)
選擇排序 (Selection Sort) 是一種簡單直觀的排序演算法。核心概念: 每一輪從尚未排序的元素中挑出最小(或最大)者,放到目前應在的位置,一直重複直到完成。可以把它想成「選拔賽」: 第一輪選出最優放第一名,第二輪從剩下的人選次優放第二名,依此類推。
到目前為止,我們已經介紹了各種資料結構 (Array、Linked List、Stack、Queue、Tree、Graph),也練習了基本操作。但光有資料結構還不夠,要能設計演算法,就必須懂得如何評估一個演算法的效率。
這篇我們要正式進入演算法分析 (Algorithm Analysis),也就是回答以下問題:
在軟體工程或面試中,我們常聽到: 「時間複雜度是多少?」、「能不能把 $O(n^2)$ 降到 $O(n \log n)$?」這些問題的本質就是演算法效率。
舉例:
所以,演算法分析的目的不是「找最炫的解法」,而是在輸入規模變大時,哪個演算法更可擴展 (scalable)。
時間複雜度衡量的是: 隨著輸入規模 $n$ 增加,演算法所需步驟數量的增長率。 這裡不考慮硬體速度、編譯器優化,而是純粹從數學角度來看,常用的表示法是 Big-O 表示法:
def get_first_element(arr):
return arr[0]
無論陣列多大,只取第一個元素,時間固定。
def find_max(arr):
max_val = arr[0]
for num in arr:
if num > max_val:
max_val = num
return max_val
需要遍歷所有元素,步驟數與 $n$ 成正比。
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
雙層迴圈,每增加一倍輸入,執行時間大約變四倍。
def binary_search(arr, target):
left, right = 0, len(arr)-1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
每次將搜尋範圍縮小一半。
例如 Merge Sort 與 Quick Sort,是大多數排序演算法的效率極限。
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr)//2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i]); i+=1
else:
result.append(right[j]); j+=1
result.extend(left[i:])
result.extend(right[j:])
return result
除了時間,演算法也需要額外的記憶體。
範例:
複雜度 | 範例 | 適用情境 |
---|---|---|
$O(1)$ | Hash 查詢 | 即時查找 |
$O(\log n)$ | Binary Search | 有序資料快速查詢 |
$O(n)$ | 遍歷陣列 | 單次掃描 |
$O(n \log n)$ | Merge Sort, Quick Sort | 高效排序 |
$O(n^2)$ | Bubble Sort, Floyd-Warshall | 小規模輸入 |
$O(2^n)$ | Subset/Knapsack 遞迴解 | 小輸入 NP 問題 |
$O(n!)$ | 全排列, TSP 暴力解 | 幾乎不可行 |
演算法分析是理解程式效率的核心能力。
理解這些概念後,我們就能判斷什麼時候該用哪種資料結構或演算法,這不僅是面試必考,也是設計高效程式的基礎。