跳轉至

建立 2026-11-12 更新 2026-11-12 SL HL

B2.4 程式演算法

程式演算法(Programming algorithms)介紹最常用的搜尋與排序演算法,以及如何用大 O 表示法(Big O notation)比較演算法效率。本頁涵蓋線性搜尋、二元搜尋、氣泡排序、選擇排序,HL 再加上遞迴、合併排序與快速排序。

如何比較演算法效率?

大 O 表示法 描述資料量 n 變大時,演算法執行步驟數的成長趨勢(通常指最差情況):

大 O 名稱 例子 n = 1000 時約幾步
O(1) 常數 用索引存取陣列元素 1
O(log n) 對數 二元搜尋 10
O(n) 線性 線性搜尋 1,000
O(n log n) 線性對數 合併排序 10,000
O(n²) 平方 氣泡排序、選擇排序 1,000,000

除了時間複雜度,也要考慮 空間複雜度:演算法需要多少額外記憶體。

線性搜尋和二元搜尋怎麼選?

線性搜尋(linear search):從頭到尾逐一比對。資料不必排序,時間 O(n)。

二元搜尋(binary search):資料 必須已排序。每次比對中間元素,排除一半資料,時間 O(log n)。

def linear_search(items, target):
    for i in range(len(items)):
        if items[i] == target:
            return i
    return -1

def binary_search(items, target):
    low, high = 0, len(items) - 1
    while low <= high:
        mid = (low + high) // 2
        if items[mid] == target:
            return mid
        elif items[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1
public static int binarySearch(int[] items, int target) {
    int low = 0, high = items.length - 1;
    while (low <= high) {
        int mid = (low + high) / 2;
        if (items[mid] == target) {
            return mid;
        } else if (items[mid] < target) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return -1;
}

氣泡排序與選擇排序

氣泡排序(bubble sort):重複比較相鄰兩個元素,順序錯就交換。每一輪會把目前最大值「浮」到尾端。若某一輪完全沒有交換,代表已排好,可提早結束。

選擇排序(selection sort):每一輪找出未排序部分的最小值,和未排序部分的第一個元素交換。

def bubble_sort(a):
    n = len(a)
    for i in range(n - 1):
        swapped = False
        for j in range(n - 1 - i):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                swapped = True
        if not swapped:
            break
    return a

def selection_sort(a):
    n = len(a)
    for i in range(n - 1):
        min_index = i
        for j in range(i + 1, n):
            if a[j] < a[min_index]:
                min_index = j
        a[i], a[min_index] = a[min_index], a[i]
    return a
演算法 最差時間 最佳時間 特色
氣泡排序 O(n²) O(n)(已排序且有提早結束) 簡單;交換次數多
選擇排序 O(n²) O(n²) 交換次數最少(最多 n − 1 次)

遞迴

HL 延伸:本節僅 HL 需要

SL 學生可略過遞迴、合併排序與快速排序。

遞迴(recursion) 是函式呼叫自己來解決較小的同類問題。每個遞迴函式必須有:

  1. 基本情況(base case):不再遞迴、直接回傳的條件,否則會無限遞迴導致 堆疊溢位(stack overflow)。
  2. 遞迴情況(recursive case):把問題縮小後呼叫自己。
def factorial(n):
    if n <= 1:              # 基本情況
        return 1
    return n * factorial(n - 1)   # 遞迴情況

# factorial(4) = 4 * factorial(3)
#              = 4 * 3 * factorial(2)
#              = 4 * 3 * 2 * factorial(1)
#              = 24

每次呼叫都會在 呼叫堆疊(call stack) 上新增一層,因此遞迴比迴圈耗記憶體;但對樹狀結構、分治法等問題,遞迴的程式碼更簡潔。

合併排序與快速排序

兩者都是 分治法(divide and conquer):

  • 合併排序(merge sort):把陣列不斷對半切到只剩 1 個元素,再兩兩合併成已排序的陣列。時間永遠 O(n log n),但需要額外 O(n) 記憶體。
  • 快速排序(quicksort):選一個基準值(pivot),把小於它的放左邊、大於它的放右邊,再分別遞迴排序兩邊。平均 O(n log n),最差(pivot 每次都選到最大或最小)O(n²),通常不需要額外陣列。
def merge_sort(a):
    if len(a) <= 1:
        return a
    mid = len(a) // 2
    left = merge_sort(a[:mid])
    right = merge_sort(a[mid:])
    result, i, j = [], 0, 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
    return result + left[i:] + right[j:]

推薦影音

大 O 表示法

簡述:HackerRank 用實例說明如何從程式碼推算時間複雜度,以及為什麼可以省略常數與低次項,對應本頁效率比較一節。

二元搜尋

簡述:說明二元搜尋的運作方式,並示範迴圈與遞迴兩種寫法,特別提醒邊界條件的處理。

氣泡排序

簡述:用圖解與程式碼說明氣泡排序,並介紹「沒有交換就提早結束」的最佳化。

遞迴

簡述:HackerRank 說明遞迴的基本情況與遞迴情況,以及如何避免重複計算,適合 HL 學生入門遞迴。

合併排序

簡述:逐步示範合併排序的切分與合併過程,並分析為什麼是 O(n log n)。

快速排序

簡述:說明 pivot 的選擇與分割(partition)過程,並比較快速排序與合併排序的優缺點。

練習題

練習 1:二元搜尋追蹤

在 [3, 8, 12, 17, 25, 31, 40] 中用二元搜尋找 31,寫出每一步的 low、high、mid。

參考答案
步驟 low high mid items[mid] 動作
1 0 6 3 17 17 < 31,low = 4
2 4 6 5 31 找到,回傳 5
練習 2:氣泡排序一輪

對 [5, 1, 4, 2, 8] 執行一輪氣泡排序,結果是什麼?

參考答案

比較 5,1 → 交換 [1,5,4,2,8];5,4 → 交換 [1,4,5,2,8];5,2 → 交換 [1,4,2,5,8];5,8 → 不換。結果 [1, 4, 2, 5, 8]。

練習 3:選擇搜尋法

一份 100 萬筆、未排序的資料只需要搜尋一次,應該先排序再二元搜尋,還是直接線性搜尋?

參考答案

直接線性搜尋。排序至少需要 O(n log n),比一次線性搜尋的 O(n) 還慢。只有需要搜尋很多次時,先排序再用二元搜尋才划算。

練習 4:寫出演算法

寫一個函式,回傳串列中第二大的數(假設至少有兩個不同的數)。

參考答案
def second_largest(nums):
    first = second = float("-inf")
    for n in nums:
        if n > first:
            second = first
            first = n
        elif first > n > second:
            second = n
    return second
HL 練習 1:遞迴

寫一個遞迴函式 power(base, exp) 計算 base 的 exp 次方(exp ≥ 0),並指出基本情況。

參考答案
def power(base, exp):
    if exp == 0:          # 基本情況
        return 1
    return base * power(base, exp - 1)
HL 練習 2:比較排序演算法

一台記憶體非常有限的嵌入式裝置要排序資料,應選合併排序還是快速排序?為什麼?

參考答案

快速排序。它可以在原陣列上就地(in-place)分割,不需要合併排序那樣 O(n) 的額外陣列。若擔心最差情況 O(n²),可以用隨機或三數取中選 pivot。