Java 欧几里得算法 欧几里得算法程序_c++

最近实验中用到了仿射加解密算法,其中的解密操作是通过扩展欧几里得算法实现的,因此在这里对

欧几里得算法、扩展欧几里得算法 做一个完整的记录。

1. 欧几里得算法(也叫辗转相除法)


1.1 直接上模拟

现在求 6 和 16 的最大公约数,根据高中知识(其实我也忘了,现学的芭芭拉迪):
每一次将除式中的除数作为下一次的被除数,将余数作为下一次的除数,直到商为 0,此时的除数就是最大公约数:

  • 16 ➗ 6 = 2 Java 欧几里得算法 欧几里得算法程序_算法_02
  • 6 ➗ 4 = 1 Java 欧几里得算法 欧几里得算法程序_最大公约数_03
  • 4 ➗ 2 = 2 Java 欧几里得算法 欧几里得算法程序_算法_02

因此,最大公约数为 2;


1.2 几何理解

假设现在需要对 a, b (不妨假设 a >b) 求最大公约数,在几何上可以这样进行理解:

  • Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_05 为矩形的边长,构造一个矩形 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_06
  • 以矩形的短边(这里为 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_07)构造正方形 B,然后计算这样的正方形 B 在矩形 A 中最多能够放置多少个;

这样就对问题进行了一个抽象:目标就是去寻找能够没有缝隙地铺满矩形 Java 欧几里得算法 欧几里得算法程序_算法_08 的正方形 Java 欧几里得算法 欧几里得算法程序_c++_09 的最大边长值;
假设上面的正方形 Java 欧几里得算法 欧几里得算法程序_c++_09 不能将矩形 Java 欧几里得算法 欧几里得算法程序_算法_08

  • 用长边剩余的长度够构建正方形,继续去填补未被覆盖的部分;
  • 以此类推,直到找到一个能够刚好无缝隙地填满未覆盖部分的正方形,这个正方形的边长就是最大公约数;

这样会产生一个疑问,怎么保证填满未覆盖部分的正方形能够填满大的矩形呢?

我们通过作图来理解:

Java 欧几里得算法 欧几里得算法程序_c++_12


可以看到正方形 Java 欧几里得算法 欧几里得算法程序_c++_09 刚好填满了正方形 Java 欧几里得算法 欧几里得算法程序_算法_08 未覆盖的部分,并且显然从最右边的 Java 欧几里得算法 欧几里得算法程序_算法_08 与两个 Java 欧几里得算法 欧几里得算法程序_c++_09 的关系可以看出 Java 欧几里得算法 欧几里得算法程序_算法_08 的边长是 Java 欧几里得算法 欧几里得算法程序_c++_09 的两倍。

那么,如果用 Java 欧几里得算法 欧几里得算法程序_c++_09 来填充 Java 欧几里得算法 欧几里得算法程序_算法_08

  • 纵向可以刚好填满,
  • 而又因为 Java 欧几里得算法 欧几里得算法程序_最大公约数_21

这就证明了:能填满未覆盖部分的 Java 欧几里得算法 欧几里得算法程序_c++_09 一定可以填满覆盖了的所有 Java 欧几里得算法 欧几里得算法程序_算法_08,这就是欧几里得算法的几何解释;
现在用函数 Java 欧几里得算法 欧几里得算法程序_最大公约数_24 来表示在长边为 Java 欧几里得算法 欧几里得算法程序_最大公约数_25,短边为 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_07

  • 长边剩余部分的长度为 Java 欧几里得算法 欧几里得算法程序_最大公约数_27
  • 短边为 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_07
  • 上述几何解释告诉我们:Java 欧几里得算法 欧几里得算法程序_欧几里得算法_29

1.3 用代数方法证明 Java 欧几里得算法 欧几里得算法程序_c++_30

1.3.1 左推右:Java 欧几里得算法 欧几里得算法程序_c++_30

现在有一个除式:Java 欧几里得算法 欧几里得算法程序_算法_32,并且 Java 欧几里得算法 欧几里得算法程序_算法_08Java 欧几里得算法 欧几里得算法程序_c++_09 的最大公约数为 Java 欧几里得算法 欧几里得算法程序_最大公约数_35,现在要求 Java 欧几里得算法 欧几里得算法程序_最大公约数_35

  • 由题可设:Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_37
  • 又因为 Java 欧几里得算法 欧几里得算法程序_最大公约数_38
  • 所以 Java 欧几里得算法 欧几里得算法程序_算法_39
  • 由于 Java 欧几里得算法 欧几里得算法程序_最大公约数_40 都是整数,因此 Java 欧几里得算法 欧几里得算法程序_最大公约数_41 也是整数,且是 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_42
  • 所以,被除数、除数、余数都有相同的公约数 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_42,且一定是最大的(假设不是最大的,则这个数一定也是被除数和除数的公约数,这与假设中的 最大公约数 矛盾)

因此,左推右得证;

1.3.2 右推左:Java 欧几里得算法 欧几里得算法程序_c++_44

这个跟左推右的证明思路一样,关于除式 Java 欧几里得算法 欧几里得算法程序_算法_32,假设 Java 欧几里得算法 欧几里得算法程序_c++_09Java 欧几里得算法 欧几里得算法程序_c++_47 的最大公约数为 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_48,现在要求 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_48

  • 由题可设:Java 欧几里得算法 欧几里得算法程序_c++_50
  • 又因为 Java 欧几里得算法 欧几里得算法程序_c++_51
  • 所以 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_52
  • 由于 Java 欧几里得算法 欧几里得算法程序_算法_53 都是整数,因此 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_06 也是整数,且是 Java 欧几里得算法 欧几里得算法程序_c++_55

因此,右推左得证;


1.4 欧几里得算法(辗转相除法)代码

展开版:

int gcd(int a, int b) {
	if (!b) {
		return a;
	}
	
	return gcd(b, a % b);
}

简化版:

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

2. 扩展欧几里得算法

扩展欧几里得算法是在欧几里得算法的运行过程中,求出方程 Java 欧几里得算法 欧几里得算法程序_最大公约数_56

2.1 直接上模拟

对于上面我们对欧几里得算法进行的模拟过程:

  • 16 ➗ 6 = 2 Java 欧几里得算法 欧几里得算法程序_算法_02
  • 6 ➗ 4 = 1 Java 欧几里得算法 欧几里得算法程序_最大公约数_03
  • 4 ➗ 2 = 2 Java 欧几里得算法 欧几里得算法程序_算法_02

可以从下到上对这些式子进行变形:

  • Java 欧几里得算法 欧几里得算法程序_最大公约数_60
  • Java 欧几里得算法 欧几里得算法程序_最大公约数_61
  • Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_62

意思就是说,一定可以找到方程 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_63


2.2 裴蜀定理

由上面的分析可以得到一个定理:
裴蜀定理:设 a, b 为正整数,则关于 x, y 的方程 ax + by = c 有整数解 当且仅当 c 是 gcd(a, b) 的倍数; 下面给出一个简要的证明:
Java 欧几里得算法 欧几里得算法程序_欧几里得算法_64
充分性按照上面的思路反推即可;
因此,假设现在要求 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_65 的一组解,只需求出 Java 欧几里得算法 欧几里得算法程序_算法_66 的一组解 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_67,然后令系数 Java 欧几里得算法 欧几里得算法程序_最大公约数_68


2.3 算法公式推导

现在看下面的方程:
Java 欧几里得算法 欧几里得算法程序_c++_69
由于 Java 欧几里得算法 欧几里得算法程序_c++_30,因此有
Java 欧几里得算法 欧几里得算法程序_算法_71
因此有
Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_72
整理得到
Java 欧几里得算法 欧几里得算法程序_算法_73
由于 Java 欧几里得算法 欧几里得算法程序_算法_66,对比两个方程,令对应系数相等可以得到:
Java 欧几里得算法 欧几里得算法程序_最大公约数_75
所以递归更新参数 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_76,即可得到答案;


下面看看递归终止条件是什么:
Java 欧几里得算法 欧几里得算法程序_欧几里得算法_77 时有:
Java 欧几里得算法 欧几里得算法程序_最大公约数_78
此时对于方程 Java 欧几里得算法 欧几里得算法程序_算法_66,有
Java 欧几里得算法 欧几里得算法程序_欧几里得算法_80
此为递归终止条件;

2.4 上代码

// d 为最大公约数
int extgcd(int a, int b, int& x, int& y) {
    if (!b) {
        x = 1;
        y = 0;
        return a;
    }

    int d = extgcd(b, a % b, x, y);
    int x0 = x, y0 = y;

    x = y0;
    y = x0 - (a / b) * y0;

    return d;
}

简化版还可以在递归的时候交换一下 Java 欧几里得算法 欧几里得算法程序_c++_81Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_82

int extgcd(int a, int b, int& x, int& y) {
    if (!b) {
        x = 1;
        y = 0;
        return a;
    }

    int d = extgcd(b, a % b, y, x);
    y -= (a / b) * x;

    return d;
}

2.5 扩展欧几里得算法的应用

2.5.1 应用一:求解不定方程

这个过程就是求 Java 欧几里得算法 欧几里得算法程序_最大公约数_56

2.5.2 应用二:求乘法逆元

首先给出乘法逆元的定义:

Java 欧几里得算法 欧几里得算法程序_最大公约数_25Java 欧几里得算法 欧几里得算法程序_c++_85 互质,则满足 Java 欧几里得算法 欧几里得算法程序_算法_86Java 欧几里得算法 欧几里得算法程序_欧几里得算法_87Java 欧几里得算法 欧几里得算法程序_最大公约数_25

举个例子:求 7 模 11 的逆元,由于 Java 欧几里得算法 欧几里得算法程序_c++_89,因此有 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_90,因此 7 在该条件下的逆元为 8;

因此,现在给出 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_91Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_92,求出的方程 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_93 的一组解 Java 欧几里得算法 欧几里得算法程序_最大公约数_94 其中的 Java 欧几里得算法 欧几里得算法程序_算法_95 就是 Java 欧几里得算法 欧几里得算法程序_欧几里得算法_91

要使用扩展欧几里得算法进行求解,需要满足 Java 欧几里得算法 欧几里得算法程序_c++_97,即 Java 欧几里得算法 欧几里得算法程序_最大公约数_25Java 欧几里得算法 欧几里得算法程序_c++_85


2.6 拓展:扩展欧几里得算法的通解

上面的过程我们只是求出了方程 Java 欧几里得算法 欧几里得算法程序_最大公约数_56

2.6.1 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_101Java 欧几里得算法 欧几里得算法程序_c++_102

首先证明 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_101Java 欧几里得算法 欧几里得算法程序_c++_102

采用反证法
假设 Java 欧几里得算法 欧几里得算法程序_c++_105Java 欧几里得算法 欧几里得算法程序_最大公约数_106 不互质,说明它们之间还有不等于 1 的公约数
假设这个公约数为 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_107
因此有 Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_108
这就违反了 gcd(a, b) 求取的是最大公约数的约定;
因此 Java 欧几里得算法 欧几里得算法程序_c++_105Java 欧几里得算法 欧几里得算法程序_最大公约数_106

2.6.2 求通解

Java 欧几里得算法 欧几里得算法程序_Java 欧几里得算法_111

欢迎补充~