學習目標

  • 理解最大公因數的概念
  • 掌握輾轉相除法 (Euclidean Algorithm) 的原理
  • 能夠實作遞迴和迭代版本的 GCD 演算法
  • 理解時間複雜度分析

1. 最大公因數的基本概念

1.1 定義

最大公因數 (GCD) 是指兩個或多個整數共有約數中最大的一個。

1.2 範例

  • GCD(12, 18) = 6
  • 12 的因數: 1, 2, 3, 4, 6, 12
  • 18 的因數: 1, 2, 3, 6, 9, 18
  • 公因數: 1, 2, 3, 6
  • 最大公因數: 6

  • GCD(17, 13) = 1(互質)

1.3 應用

  • 分數的約分
  • 密碼學(RSA 演算法)
  • 模運算
  • 演算法設計

2. 計算 GCD 的方法

2.1 方法 1: 列舉法

列出所有公因數,找出最大的。

缺點: 效率低,當數字很大時不實用。

2.2 方法 2: 質因數分解

將兩個數分解為質因數,找出共同的質因數。

範例: GCD(12, 18) - 12 = 2² × 3 - 18 = 2 × 3² - 共同質因數: 2¹ × 3¹ = 6

缺點: 質因數分解本身就很困難。

2.3 方法 3: 輾轉相除法(推薦)

輾轉相除法 (Euclidean Algorithm) 是最有效的方法。

3. 輾轉相除法 (Euclidean Algorithm)

3.1 原理

關鍵性質: GCD(a, b) = GCD(b, a mod b)

其中 a mod b 是 a 除以 b 的餘數。

3.2 演算法步驟

  1. 如果 b = 0,則 GCD(a, b) = a
  2. 否則,計算 r = a mod b
  3. 遞迴計算 GCD(b, r)

3.3 範例

計算 GCD(48, 18):

步驟 1: - 48 ÷ 18 = 2 餘 12 - GCD(48, 18) = GCD(18, 12)

步驟 2: - 18 ÷ 12 = 1 餘 6 - GCD(18, 12) = GCD(12, 6)

步驟 3: - 12 ÷ 6 = 2 餘 0 - GCD(12, 6) = GCD(6, 0)

步驟 4: - GCD(6, 0) = 6(因為任何數與 0 的 GCD 是該數本身)

結果: GCD(48, 18) = 6

3.4 執行過程表

a b a mod b 說明
48 18 12 48 ÷ 18 = 2 ... 12
18 12 6 18 ÷ 12 = 1 ... 6
12 6 0 12 ÷ 6 = 2 ... 0
6 0 - GCD = 6

4. 程式實作

4.1 遞迴版本

int gcd(int a, int b) {
    // 基礎情況:如果 b 為 0,則 a 就是 GCD
    if (b == 0)
        return a;

    // 遞迴呼叫:GCD(a, b) = GCD(b, a mod b)
    return gcd(b, a % b);
}

執行過程 (GCD(48, 18)):

gcd(48, 18)
  → gcd(18, 48 % 18) = gcd(18, 12)
    → gcd(12, 18 % 12) = gcd(12, 6)
      → gcd(6, 12 % 6) = gcd(6, 0)
        → 返回 6

4.2 迭代版本

int gcd(int a, int b) {
    int temp;

    // 確保 a >= b
    if (a < b) {
        temp = a;
        a = b;
        b = temp;
    }

    // 輾轉相除直到 b 為 0
    while (b != 0) {
        temp = b;
        b = a % b;
        a = temp;
    }

    return a;
}

執行過程 (GCD(48, 18)):

初始: a = 48, b = 18

迭代 1:
  temp = 18
  b = 48 % 18 = 12
  a = 18

迭代 2:
  temp = 12
  b = 18 % 12 = 6
  a = 12

迭代 3:
  temp = 6
  b = 12 % 6 = 0
  a = 6

b == 0,返回 a = 6

4.3 簡化版本(不需要交換)

int gcd(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

說明: 即使 a < b,第一次迭代後也會自動交換。

範例: GCD(18, 48)

初始: a = 18, b = 48

迭代 1:
  temp = 48
  b = 18 % 48 = 18
  a = 48
  (現在 a = 48, b = 18,已經交換)

迭代 2:
  temp = 18
  b = 48 % 18 = 12
  a = 18
  ...

4.4 處理負數

int gcd(int a, int b) {
    // 取絕對值
    if (a < 0) a = -a;
    if (b < 0) b = -b;

    if (b == 0)
        return a;
    return gcd(b, a % b);
}

5. 擴展歐幾里得演算法

5.1 概念

擴展歐幾里得演算法不僅計算 GCD,還找到整數 x 和 y,使得:

ax + by = GCD(a, b)

5.2 實作

int extendedGcd(int a, int b, int *x, int *y) {
    if (b == 0) {
        *x = 1;
        *y = 0;
        return a;
    }

    int x1, y1;
    int gcd = extendedGcd(b, a % b, &x1, &y1);

    *x = y1;
    *y = x1 - (a / b) * y1;

    return gcd;
}

6. 時間複雜度分析

6.1 理論分析

輾轉相除法的時間複雜度為 O(log min(a, b))

原因: 每次迭代,較大的數至少減少一半。

6.2 最壞情況

費波那契數列 會產生最壞情況: - GCD(F(n), F(n-1)) 需要 n 次迭代 - 其中 F(n) 是第 n 個費波那契數

6.3 實際效能

  • 對於 64 位元整數,最多需要約 64 次迭代
  • 實際執行非常快速

7. 最小公倍數 (LCM)

7.1 定義

最小公倍數 (LCM) 是指兩個或多個整數公有的倍數中最小的一個。

7.2 與 GCD 的關係

LCM(a, b) = (a × b) / GCD(a, b)

7.3 實作

int lcm(int a, int b) {
    return (a * b) / gcd(a, b);
}

注意: 為了避免溢位,可以寫成:

int lcm(int a, int b) {
    return a / gcd(a, b) * b;
}

8. 練習題

練習題 1

使用輾轉相除法計算 GCD(100, 35),逐步展示過程。

解答:

a b a mod b
100 35 30
35 30 5
30 5 0
5 0 -

結果: GCD(100, 35) = 5

練習題 2

實作一個函數,計算三個數的最大公因數。

解答:

int gcdThree(int a, int b, int c) {
    return gcd(gcd(a, b), c);
}

原理: GCD(a, b, c) = GCD(GCD(a, b), c)

練習題 3

為什麼輾轉相除法的時間複雜度是 O(log min(a, b))?

解答: 在每次迭代中,我們計算 a mod b。如果 a ≥ b,則 a mod b < a/2(因為 b ≥ a/2 時,a mod b = a - b < a/2;b < a/2 時,a mod b < b < a/2)。因此,每兩次迭代,較大的數至少減少一半,所以總迭代次數為 O(log min(a, b))。

9. 考試重點提醒

  1. 輾轉相除法原理: GCD(a, b) = GCD(b, a mod b)
  2. 遞迴和迭代實作: 兩種版本都要會
  3. 執行過程: 要能逐步展示計算過程
  4. 時間複雜度: O(log min(a, b))
  5. LCM 計算: 使用 LCM = (a × b) / GCD(a, b)