欧几里得算法和扩展欧几里得算法


超级详细的基础算法和数据结构合集:
https://blog.csdn.net/GD_ONE/article/details/104061907

摘要

本文主要讲解欧几里得算法和扩展欧几里得算法。

欧几里得算法

欧几里得算法就是辗转相除法,用于求两个数的最大公约数。

g c d ( a , b ) gcd(a, b) gcd(a,b) 表示a和b的最大公约数。
辗转相除法的核心就是 g c d ( a , b ) = g c d ( b , a % b ) gcd(a, b) = gcd(b, a\%b) gcd(a,b)=gcd(b,a%b),当 a % b = = 0 a\%b == 0 a%b==0时, b b b就是最大公约数。
将以上式子转化为递归代码就是欧几里得算法。

欧几里得算法代码:

public static int gcd(int a, int b){
	if(b == 0){ // 当b==0,说明上一层的a%b == 0,所以直接返回a.
		return a;
	}
	return gcd(b, a%b);
}

那么什么是扩展欧几里得算法呢?

扩展欧几里得算法

扩展欧几里得算法是对欧几里得算法的扩展,用于求解二元一次方程的整数解。
欧几里得算法是求公约数的,跟二元一次方程能扯上什么关系?
这个关系就是 裴蜀定理

若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立

简单来说就是,如果两个整数的最大公约数是d, 那么方程 a x + b y = d ax+by=d ax+by=d一定有解。这是已经被前人证明过的定理,所以我们直接拿来用就好了,不必纠结证明过程。

那么我们知道了 a x + b y = d ax + by = d ax+by=d这个式子后怎么求解 x x x y y y呢。
x 和 y x和y xy有很多组解,我们可以知道一组特解是:当 a % b = = 0 a\%b == 0 a%b==0时, a = = d a==d a==d,此时令 x = 1 , y = 0 x = 1, y = 0 x=1,y=0,得到: a * 1 + 0 = d, 显然,x = 1, y = 0,是ax + by = d的一组特解。
那么当 a % b ! = 0 a\%b!=0 a%b!=0时怎么办?
因为 g c d ( a , b ) = = g c d ( b , a % b ) gcd(a, b) == gcd(b, a\%b) gcd(a,b)==gcd(b,a%b),它们之间是存在一定联系的,所以我们不妨试试能否利用这个式子得到通解。
已知:
a ∗ x 1 + b ∗ y 1 = g c d ( a , b ) a*x_1 + b*y_1 = gcd(a,b) ax1+by1=gcd(a,b)
b ∗ x 2 + ( a % b ) ∗ y 2 = g c d ( b , a % b ) b*x_2 + (a\%b)*y_2 = gcd(b, a\%b) bx2+(a%b)y2=gcd(b,a%b)
g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b) = gcd(b, a\%b) gcd(a,b)=gcd(b,a%b);

显然:
a ∗ x 1 + b ∗ y 1 = b ∗ x 2 + ( a % b ) ∗ y 2 a*x_1 + b*y_1 = b*x_2 + (a\%b)*y_2 ax1+by1=bx2+(a%b)y2, 接下来想办法消掉 % \% %号,统一为常见的乘法,以便合并同类项。
因为: a % b = a − b ∗ ( a / b ) a\%b = a - b*(a/b) a%b=ab(a/b)
所以: a ∗ x 1 + b ∗ y 1 = b ∗ x 2 + ( a − b ∗ ( a / b ) ) ∗ y 2 a*x_1 + b*y_1 = b*x_2 + (a - b*(a/b))*y_2 ax1+by1=bx2+(ab(a/b))y2
将上式展开: a ∗ x 1 + b ∗ y 1 = b ∗ x 2 + a ∗ y 2 − b ∗ ( a / b ) ∗ y 2 a*x_1 + b*y_1 = b*x_2 + a*y2 - b*(a/b)*y2 ax1+by1=bx2+ay2b(a/b)y2
将上式整理: a ∗ x 1 + b ∗ y 1 = a ∗ y 2 + b ∗ ( x 2 − ( a / b ) ∗ y 2 ) a*x_1 + b*y_1 = a*y2+ b*(x2-(a/b)*y2) ax1+by1=ay2+b(x2(a/b)y2)

显然: x 1 = y 2 , y 1 = x 2 − ( a / b ) ∗ y 2 x1 =y2, y1 = x2-(a/b)*y2 x1=y2,y1=x2(a/b)y2
于是我们就得到了解之间的关系,接下来通过一组特解,就可以得到其他解。

我们知道,当 a % b = = 0 a\%b ==0 a%b==0时, a x + b y = d ax+by = d ax+by=d的解是 x = 1 , y = 0 x = 1, y = 0 x=1,y=0
而求解 g c d ( a , b ) gcd(a, b) gcd(a,b)时, 当 a % b = = 0 a\%b==0 a%b==0时,是递归出口,之后递归函数便会一层一层返回,我们正好可以利用这个性质,求解每一层 a ∗ x + b ∗ y = d a*x+b*y = d ax+by=d的解。

代码:

static int x, y;
public static int exgcd(int a, int b){
    if(b == 0){
        x = 1; 
        y = 0;
        return a;
    }
    int d = exgcd(b, a%b);
    int t = y;
    y = x - a/b*y;
    x = t; 
    return d;
}

因为这是一个递归函数,所以当执行到 b = = 0 b == 0 b==0时,函数开始一层层返回,最底层是 a = d , x = 1 , y = 0 a = d, x = 1, y = 0 a=d,x=1,y=0,我们可以在函数返回的过程中依次求出每一层的解,当函数执行完毕时, 我们就可以得到原式的一个特解。
何时无解:
根据定义,对于 a ∗ x + b ∗ y = d , 如 果 d 是 g c d ( a , b ) a*x+b*y=d, 如果d是gcd(a,b) ax+by=ddgcd(a,b)的倍数,则该式有解,所以如果 d d%gcd(a,b) != 0 d,则该式无解。

最小正整数解

a ∗ x + b ∗ y = d a*x+b*y = d ax+by=d是有很多组解的,我们怎么得到x的最小整数解呢?

( x 1 , y 1 ) , ( x 2 , y 2 ) 是 a ∗ x + b ∗ y = d 的 两 组 解 (x_1,y_1), (x_2, y_2)是a*x+b*y=d的两组解 (x1y1),(x2,y2)ax+by=d
则有: a ∗ x 1 + b ∗ y 1 = a ∗ x 2 + b ∗ y 2 a*x_1 + b*y_1 = a*x_2 + b*y_2 ax1+by1=ax2+by2
整理: a ∗ ( x 1 − x 2 ) = b ∗ ( y 2 − y 1 ) a*(x_1-x_2) = b*(y_2-y_1) a(x1x2)=b(y2y1)

等式两边同时除以gcd(a,b):
得到: a / g c d ( a , b ) ∗ ( x 1 − x 2 ) = b / g c d ( a , b ) ∗ ( y 2 − y 1 ) a/gcd(a,b)*(x_1-x_2) = b/gcd(a,b)*(y_2-y_1) a/gcd(a,b)(x1x2)=b/gcd(a,b)(y2y1)

a ′ = a / g c d ( a , b ) , b ′ = b / g c d ( a , b ) a' = a/gcd(a,b), b'= b/gcd(a,b) a=a/gcd(a,b),b=b/gcd(a,b)
显然: a ′ 与 b ′ 互 质 a'与b'互质 ab
所以对于: a ′ ∗ ( x 1 − x 2 ) = b ′ ∗ ( y 2 − y 1 ) a'*(x_1-x_2) = b'*(y_2-y_1) a(x1x2)=b(y2y1) ( x 1 − x 2 ) 是 b ′ 的 整 倍 数 , ( y 2 − y 1 ) 是 a ′ 的 整 倍 数 (x_1 - x_2)是b'的整倍数,(y_2-y_1)是a'的整倍数 (x1x2)b,(y2y1)a
就好比 3 ∗ 10 = 2 ∗ 15 3* 10 = 2 *15 310=215
所以: x 1 = x 2 + b ′ ∗ k ( k = 1 , 2 , 3 , . . . ) x_1 = x_2 + b'*k(k = 1,2,3,...) x1=x2+bk(k=1,2,3,...)
x 的 值 是 每 b ′ 一 循 环 , 所 以 直 接 让 x % b ′ x的值是每b'一循环,所以直接让x\%b' xbx%b就可以得到最小的解了,但求出的解可能是负数,所以先让 x % b x\%b x%b,然后再让 x x x加上一个 b ′ b' b,然后再对 b ′ b' b取余。
即: x = ( x % b ′ + b ′ ) % b ′ x = (x\%b'+b')\%b' x=(x%b+b)%b

模板题:
扩展欧几里得算法
代码:

import java.io.*;
import java.util.*;

public class Main{
    
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    public static int Int(String s){return Integer.parseInt(s);}
    static int x, y;
    public static int exgcd(int a, int b){
        if(b == 0){
            x = 1;
            y = 0;
            return a;
        }
        int d = exgcd(b, a%b);
        int t = y;
        y = x - a/b*y;
        x = t;
        return d;
    }
    public static void main(String[] args) throws Exception{
        int n = Int(in.readLine());
        while(n --> 0){
            String[] s = in.readLine().split(" ");
            int a = Int(s[0]);
            int b = Int(s[1]);
            exgcd(a, b);
            out.write(x+" "+y+"\n");
        }
        out.flush();
    }
} 

例题:
线性同余方程

代码:

import java.io.*;
import java.util.*;

public class Main{
    
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    public static int Int(String s){return Integer.parseInt(s);}
    static int x, y;
    public static int exgcd(int a, int b){
        if(b == 0){
            x = 1;
            y = 0;
            return a;
        }
        int d = exgcd(b, a%b);
        int t = y;
        y = x - a/b*y;
        x = t;
        return d;
    }
    public static void main(String[] args) throws Exception{
        int n = Int(in.readLine());
        while(n --> 0){
            String[] s = in.readLine().split(" ");
            int a = Int(s[0]);
            int b = Int(s[1]);
            int m = Int(s[2]);
            int d = exgcd(a, m);
            int c = b/d;
            int t = m/d;
            if(b % d != 0){
                out.write("impossible\n");
            }
            else out.write(((long)x*c)%t + "\n");
        }
        out.flush();
    }
} 
  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
扩展欧几里得算法是求解一元一次不定方程 ax + by = gcd(a,b) 的一种方法,其中 a 和 b 是整数,gcd(a,b) 是它们的最大公约数,x 和 y 是整数解。逆元是指在模运算下,一个数的乘法逆元是指与它相乘后模运算得到 1 的数。在数论中,常常需要求一个数在模意义下的逆元,即一个数 k 满足 (k * x) % m = 1,其中 m 是模数。 下面是扩展欧几里得算法求逆元的 C 语言实现: ```c #include <stdio.h> // 扩展欧几里得算法 int exgcd(int a, int b, int *x, int *y) { if (b == 0) { *x = 1; *y = 0; return a; } int gcd = exgcd(b, a % b, y, x); *y -= a / b * (*x); return gcd; } // 求逆元 int modinv(int a, int m) { int x, y; int gcd = exgcd(a, m, &x, &y); if (gcd != 1) { return -1; // a 和 m 不互质,不存在逆元 } else { return (x % m + m) % m; // 转化为正整数 } } int main() { int a = 3, m = 11; int inv = modinv(a, m); if (inv == -1) { printf("%d 在模 %d 意义下不存在逆元\n", a, m); } else { printf("%d 在模 %d 意义下的逆元是 %d\n", a, m, inv); } return 0; } ``` 这个程序中,exgcd 函数通过递归实现扩展欧几里得算法,返回 a 和 b 的最大公约数,并且求出 x 和 y 的值。在 modinv 函数中,我们调用 exgcd 函数求出 a 和 m 的最大公约数,并且判断 a 和 m 是否互质,如果不互质则不存在逆元。否则,根据扩展欧几里得算法的结果,求出 x 的值作为 a 在模 m 意义下的逆元。注意,由于 x 可能是负数,所以要将其转化为正整数。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值