Skip to content

歐幾里得演算法;輾轉相除法;最大公因數gcd

from:2024清華大學IONC講義

函數製作

要設計一個演算法去計算 \(a, b\) 的最大公因數。最直觀的想法是枚舉所有可能的因數。

code
int gcd(int a, int b) {
    for (int i = min(a, b); i >= 1; i--) {
        if (a % i == 0 && b % i == 0) return i;
    }
    return 1;
}

不失一般性假設 \(a ≤ b\),那其時間複雜度為 \(O(a)\)。但是我們希望能有更快的演算法來求出最大的公因數! 我們能夠想到一個(所以學過的東西何故輕易相忘,輾轉間你會想到兩個偶感)極為簡單的數學道理 其中較小的數和兩數的差的最大公因數,因此我們可以導出一個遞迴公式如下。

code
int gcd(int a, int b) {
    if (a < b) swap(a, b);
    if (b == 0) return a;
    return gcd(b, a - b);
}

這個函式能用較快的速度計算如 206 和 100 的最大公因數,並不需要從 100 開始枚舉到 1 來判斷! 但對於極端情況 gcd(2, 2000000000),遞迴呼叫 gcd 達億次。

我們繼續觀察上述的極端情況。遞迴呼叫 gcd 時,\(a - b\) 一直沒有變化,反倒是 \(b\) 一直減到 \(b < a\) 為止! 因此我們可以發現其實一直減不如直接用輾轉相除求出 \(b < a\) 的情形其數值是會多一分,而且一個數字被有效取餘時,新的值至少為原來的一半,所以能提是足快達的求出答案。最後我們能整出下列的寫法,其時間複雜度約為 \(O(log a)\)

code
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

內建函數

注意前面是兩個底線

code
#include<bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(0);cin.tie(0);
#define all(x) x.begin(),x.end()


int main(){io
    stringstream cin("5,10");

    int n,m;
    cin>>n>>m;

    cout<<__gcd(n,m);


}