擴展歐幾里得算法


本文是我上篇博客《最大公約數》的延續,見 https://www.cnblogs.com/hanselhuang/p/gcd.html

 

擴展歐幾里得算法(簡稱擴歐)是歐幾里得算法(又稱輾轉相除法)的擴展。擴歐可以在求得 a、b的最大公約數的同時,能找到整數 x、y,使它們滿足貝祖等式:ax+by = gcd(a, b) 。如果 a 是負數,可以把問題轉化成 |a| (-x) + by = gcd(|a|, b) ,然后令 x' = (-x) 。

例如 a=6,b=9,使用歐幾里得算法我們可以得到 gcd(6, 9) = 3,而使用擴歐算法,我不僅可以計算得到最大公約數 3 ,而且可以得到方程 6x+9y = 3 的整數解 x、y 。貝祖定理告訴我們該方程必有整數解,且可以簡單證明:

方程 ax+by = c 有整數解  <=>  c 是 a 和 b 的最大公約數的倍數,即 c % gcd(a, b) == 0 成立。

證明:

方程 ax+by = c 有整數解,即 x、y為整數

則 ax % gcd(a, b) == 0 成立

by % gcd(a, b) == 0 成立

=> (ax+by) % gcd(a, b) == 0

=> c % gcd(a, b) == 0

證明完畢。

 

前面我們一直圍繞 ax+by = gcd(a, b) 這個方程做思考,其實這個方程就是線性同余方程。線性同余方程的表述為 ax ≡ b ( mod n ) ,三橫表示恆等於,此方程有解當且僅當 b 能被 a 與 n 的最大公約數整除,此時,如果 x0 是方程的一個解,那么方程的通解可以表示為 { x0 + kn / gcd(a, n) | (k∈Z) } 。有兩個問題需要解決:為什么說貝祖等式與線性同余方程等價;方程通解的推導。

1)貝祖等式與線性同余方程等價:

ax ≡ b ( mod n )  <=>  ax mod n = b mod n

b mod n = b - n ⌊b/n⌋( 向下取整 )

ax mod n = ax - n ⌊ax/n⌋

則 b - n ⌊b/n⌋ = ax - n ⌊ax/n⌋

令 y = ⌊b/n⌋ - ⌊ax/n⌋( y 值域為整數 )

=> ax + ny = b

轉化成了貝祖等式。

2)方程通解的推導:

在推導前,我們先驗證下公式,例如方程 6x ≡ 3 ( mod 9 ) 的一個特解為 5( 把方程看成 6x mod 9 = 3 mod 9 ),則其通解可以表示為 5 + 9k / 3 = 5 + 3k ,k∈Z 。代入 k = -1,1,2 分別得到 x1 = 2,x2 = 8,x3 = 11 。可以代入原方程進行驗算,都是正確的。

我們從貝祖等式 ax+by = gcd(a, b) 入手推導其通解:

設 x0、y0 為方程的一組特解

則 a*x0 + b*y0 = gcd(a, b)

用原等式減上式得:

a (x - x0) + b (y - y0) = 0

兩邊同時除以 gcd(a, b)

(x - x0) a / gcd(a, b) = (y0 - y) b / gcd(a, b)

容易知道 a / gcd(a, b) 與 b / gcd(a, b) 互質

要使方程左右兩邊相等,則 (x - x0) 必須是 b / gcd(a, b) 的倍數

同理, (y - y0) 必須是 a / gcd(a, b) 的倍數

所以 x - x0 = b / gcd(a, b) * t,y0 - y = a / gcd(a, b) * t ,( t∈Z )

所以 x = x0 + b / gcd(a, b) * t,y = y0 - a / gcd(a, b) * t ,( t∈Z )

貝祖等式通解推導完畢。

因為貝祖等式可以等價成線性同余方程,所以可得線性同余方程 ax ≡ b ( mod n ) 的通解為 { x0 + kn / gcd(a, n) | (k∈Z) } 。

 

使用擴歐算法可以得到線性同余方程的特解,根據這個特解可以得到方程的通解。

 

擴展歐幾里得算法的C語言代碼:

#include <stdio.h>

// 返回最大公約數 
int e_gcd(int a, int b, int &x, int &y) {
    if(b == 0) {
        x = 1; y = 0;
        return a;
    }
    int x1;
    int d = e_gcd(b, a % b, x1, x);
    y = x1 - a / b * x;
    return d;
}

int main() {
    int a=6, b=9;
    int x0, y0, gcd;
    gcd = e_gcd(a, b, x0, y0);
    
    printf("x0:%d y0:%d gcd:%d", x0, y0, gcd);
    // x = x0 + (b/gcd)*t
    // y = y0 – (a/gcd)*t
    
    // 6x + 9y = 3
    // x = -1 + 3*t
    // y =  1 - 2*t
    
    return 0;
}

可以看到是用遞歸的方式來寫的,要理解代碼的原理,首先我們看看歐幾里得算法的遞歸寫法:

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

歐幾里得算法的原理是 gcd(a, b) = gcd(b, a mod b) ,通過連續計算余數,直到余數等於 0 為止,最后得到的非 0 余數就是最大公約數。

那么我們如何從歐幾里得算法得到擴展歐幾里得算法呢?

我們知道方程 ax+by = gcd(a, b) 一定有整數解,我們要做的就是求出一個特解出來。

我們注意到算法的終止條件是 b = 0 ,a = gcd 。我們根據方程 ax+by = gcd(a, b) 構造這樣一個等式 gcd * x1 + 0 * y1 = gcd ,可得 x1 = 1 ,y1 = 0 為一組整數解( y 有無窮個整數解 ),作為算法的最終狀態。

那么我們返回算法的前一個狀態,此時我們已經求得 b 和 a%b 的最大公約數為 gcd ,並且求得了一組整數解 x1 和 y1 使等式 b * x1 + (a%b) * y1 = gcd 成立。我們知道 a%b = a - b ⌊a/b⌋ ,代入前式可以得到:

b * x1 + ( a - b ⌊a/b⌋ ) * y1 = gcd

=> a * y1 + b * ( x1 - y1 ⌊a/b⌋ ) = gcd

則有 x2 = y1 ,y2 = x1 - y1 ⌊a/b⌋ 為該狀態的一組整數解。

我們通過遞歸不斷返回上一個狀態,直到回到最初狀態,就能求出最初方程的一個特解了。

由此我們可以寫出擴歐的遞歸算法:

// 返回最大公約數 
int e_gcd(int a, int b, int &x, int &y) {
    if(b == 0) {
        x = 1; y = 0;
        return a;
    }
    int x1;
    int d = e_gcd(b, a % b, x1, x);
    y = x1 - a / b * x;
    return d;
}

我們得到方程 ax+by = gcd(a, b) 的一組特解 x0、y0 后,就可以得到方程的通解:x = x0 + b / gcd(a, b) * t ,y = y0 - a / gcd(a, b) * t ,( t∈Z ) 。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM