欧几里得算法与扩展欧几里得算法,C语言编程实现(零基础全解析)

欧几里得算法与扩展欧几里得算法(零基础全解析)

一、需求理解与核心目标

你希望我基于欧几里得算法、贝祖等式(裴蜀定理)、扩展欧几里得算法的核心概念,从初学者视角 完成完整的代码实现,包含递归/非递归版本,并配套详细的原理讲解、流程表格、逻辑流程图,同时保证内容的完整性和易懂性。

作为编程和数论入门教程,我会从"概念通俗化解释→数学证明→代码实现→案例验证→流程图可视化"的逻辑展开,确保你不仅能看懂代码,还能理解背后的数学原理和编程思想,最终达到"知其然,更知其所以然"的目标。

二、核心概念铺垫(初学者必看)

在正式讲解算法前,先明确几个基础概念,避免因术语障碍影响理解:

1. 最大公约数(GCD)

  • 定义 :两个整数ab的最大公约数(Greatest Common Divisor),是能同时整除ab的最大正整数,记为gcd(a,b)
  • 示例gcd(12,42)=6,因为6是能同时整除12和42的最大数(6|12,6|42,且没有比6更大的数满足此条件)。
  • 特殊情况 :若b=0,则gcd(a,0)=a(因为任何数都能整除0,而a的最大约数是它本身)。

2. 整除与余数

  • 整除 :若整数a除以整数bb≠0),商为整数且余数为0,则称b整除a,记为b|a
  • 带余除法 :对任意整数a和正整数b,存在唯一的整数q(商)和r(余数),满足a = q×b + r,且0 ≤ r < b。 示例:47 = 1×30 + 17q=1r=17);30 = 1×17 + 13q=1r=13)。

3. 贝祖等式(裴蜀定理)的通俗理解

贝祖等式解决的核心问题是:给定两个整数a和b,是否存在整数x和y,使得ax + by = m成立?

  • 结论1:当且仅当mgcd(a,b)的倍数时,等式有整数解;
  • 结论2:若ax + by = gcd(a,b)有一组解(x₀,y₀),则所有解可表示为: x = x₀ + k×(b/d)y = y₀ - k×(a/d)d=gcd(a,b)k为任意整数);
  • 特殊情况:当gcd(a,b)=1(即a和b互质)时,ax + by = 1必有整数解。

三、欧几里得算法(辗转相除法)

1. 算法原理

欧几里得算法的核心公式:
gcd(a,b) = gcd(b, a%b)a%b表示a除以b的余数)
证明(初学者友好版):

  • d = gcd(a,b),则d|ad|b
  • a = q×b + rr = a%b),可得r = a - q×b
  • 因为d|ad|b,所以d|(a - q×b),即d|r
  • 因此dbr的公约数;
  • 反之,若d'br的公约数,则d'|bd'|r,可得d'|(q×b + r) = a,即d'ab的公约数;
  • 综上,ab的公约数集合与br的公约数集合完全相同,因此最大公约数也相同。

2. 递归实现(简洁易理解)

代码实现
复制代码
复制代码
#include <iostream>
using namespace std;

// 递归实现欧几里得算法
// 参数:a-整数1,b-整数2
// 返回值:a和b的最大公约数
int gcd_recursive(int a, int b) {
    // 终止条件:b=0时,最大公约数是a
    if (b == 0) {
        return a;
    }
    // 递归调用:gcd(a,b) = gcd(b, a%b)
    return gcd_recursive(b, a % b);
}

// 测试函数
int main() {
    int a, b;
    cout << "请输入两个整数(用空格分隔):";
    cin >> a >> b;
    // 处理负数:最大公约数是正整数,先取绝对值
    a = abs(a);
    b = abs(b);
    int result = gcd_recursive(a, b);
    cout << "gcd(" << a << ", " << b << ") = " << result << endl;
    return 0;
}
代码解析(初学者视角)
  • abs(a)/abs(b):处理负数输入,因为最大公约数是正整数(例如gcd(-12,42)=6);
  • 终止条件if(b==0):对应gcd(a,0)=a的特殊情况;
  • 递归调用gcd_recursive(b, a%b):严格遵循欧几里得算法的核心公式;
  • 示例运行:输入47 30,递归过程为: gcd(47,30) → gcd(30,17) → gcd(17,13) → gcd(13,4) → gcd(4,1) → gcd(1,0),最终返回1
测试用例
输入(a,b) 输出(gcd) 说明
47 30 1 47和30互质
12 42 6 6是12和42的最大公约数
0 5 5 gcd(0,5)=5
-18 24 6 负数取绝对值后计算

3. 非递归实现(避免栈溢出)

递归实现虽然简洁,但如果输入的数极大(例如a=10^9),可能导致递归栈溢出。非递归实现通过循环替代递归,更稳定。

代码实现
复制代码
复制代码
#include <iostream>
#include <cstdlib> // abs函数头文件
using namespace std;

// 非递归实现欧几里得算法
int gcd_iterative(int a, int b) {
    // 处理负数:取绝对值
    a = abs(a);
    b = abs(b);
    // 循环条件:b≠0
    while (b != 0) {
        // 保存当前b的值
        int temp = b;
        // 更新b为a%b
        b = a % b;
        // 更新a为原来的b(即temp)
        a = temp;
    }
    // 循环结束时b=0,a即为最大公约数
    return a;
}

// 测试函数
int main() {
    int a, b;
    cout << "请输入两个整数(用空格分隔):";
    cin >> a >> b;
    int result = gcd_iterative(a, b);
    cout << "gcd(" << abs(a) << ", " << abs(b) << ") = " << result << endl;
    return 0;
}
代码解析(初学者视角)
  • 循环逻辑:每次迭代中,将ab更新为ba%b,直到b=0

  • 变量temp:用于临时保存b的值,因为更新b后会覆盖原有值;

  • 示例运行(输入47 30):

    迭代次数 a b a%b
    初始 47 30 17
    第1次 30 17 13
    第2次 17 13 4
    第3次 13 4 1
    第4次 4 1 0
    第5次 1 0 -
    循环结束,返回a=1

4. 欧几里得算法流程图

四、贝祖等式(裴蜀定理)深度解析

1. 定理完整表述

对任意整数ab,设d = gcd(a,b),则:

  • 方程ax + by = m有整数解的充要条件是d | m(即md的倍数);
  • (x₀,y₀)ax + by = d的一组解,则所有解为: x = x₀ + k×(b/d)
    y = y₀ - k×(a/d)
    k为任意整数)

2. 通俗证明(初学者版)

步骤1:证明存在性(至少有一组解)
  • S = { ax + by | x,y ∈ Z, ax + by > 0 },即所有能表示为ax+by形式的正整数集合;
  • S非空(例如|a| = a×1 + b×0 ∈ S|b| = a×0 + b×1 ∈ S);
  • 由自然数的良序性,S中存在最小正整数d₀ = a x₀ + b y₀
  • 证明d₀ = gcd(a,b)
    1. a做带余除法:a = q×d₀ + r0 ≤ r < d₀);
    2. r = a - q×d₀ = a - q(ax₀ + by₀) = a(1 - qx₀) + b(-qy₀)
    3. r > 0,则r ∈ S,但r < d₀,与d₀S中最小正整数矛盾;
    4. 因此r = 0,即d₀ | a;同理可证d₀ | b
    5. d'ab的任意公约数,则d' | ax₀ + by₀ = d₀,因此d₀是最大公约数;
  • 综上,ax₀ + by₀ = gcd(a,b),即至少存在一组解(x₀,y₀)
步骤2:证明解的通式
  • (x₀,y₀)ax + by = d的一组解,(x,y)是任意解,则: a(x - x₀) + b(y - y₀) = 0a(x - x₀) = -b(y - y₀)
  • 两边除以d,得(a/d)(x - x₀) = -(b/d)(y - y₀)
  • 因为a/db/d互质,所以a/d | (y - y₀),设y - y₀ = k×(a/d)k∈Z);
  • 代入得x - x₀ = -k×(b/d)
  • 因此通解为:x = x₀ - k×(b/d)y = y₀ + k×(a/d)(与前文形式等价,仅k符号不同)。

3. 示例验证

12x + 42y = 6为例(d = gcd(12,42)=6):

  • 一组特解:x=-3y=112×(-3) + 42×1 = -36 + 42 = 6);
  • 通解计算:b/d = 42/6 = 7a/d = 12/6 = 2
  • 所有解:x = -3 + 7ky = 1 - 2kk∈Z);
  • 验证k=1x=-3+7=4y=1-2=-112×4 + 42×(-1) = 48 - 42 = 6,正确;
  • 验证k=2x=-3+14=11y=1-4=-312×11 + 42×(-3) = 132 - 126 = 6,正确。

五、扩展欧几里得算法

1. 算法核心目标

在计算gcd(a,b)的同时,求出贝祖等式ax + by = gcd(a,b)的一组特解(x,y)

2. 递归实现(易理解,推荐初学者)

算法推导(递推关系)

设:

  • ax₁ + by₁ = gcd(a,b)
  • bx₂ + (a%b)y₂ = gcd(b, a%b); 由欧几里得算法,gcd(a,b) = gcd(b, a%b),因此: ax₁ + by₁ = bx₂ + (a%b)y₂; 又a%b = a - (a/b)×ba/b为整数除法),代入得: ax₁ + by₁ = bx₂ + (a - (a/b)×b)y₂; 整理右边:ay₂ + b(x₂ - (a/b)y₂); 根据恒等定理(等式两边ab的系数分别相等): x₁ = y₂y₁ = x₂ - (a/b)×y₂终止条件 :当b=0时,gcd(a,0)=a,此时x=1y=0(因为a×1 + 0×0 = a)。
代码实现
复制代码
复制代码
#include <iostream>
#include <cstdlib>
using namespace std;

// 递归实现扩展欧几里得算法
// 参数:a,b-输入整数;x,y-引用传递,用于返回贝祖等式的一组解
// 返回值:a和b的最大公约数
int exgcd_recursive(int a, int b, int &x, int &y) {
    // 终止条件:b=0
    if (b == 0) {
        x = 1;  // a×1 + 0×0 = a
        y = 0;
        return a;
    }
    // 递归调用,求解gcd(b, a%b),并得到x2,y2
    int d = exgcd_recursive(b, a % b, x, y);
    // 保存当前y(即y2)
    int temp = y;
    // 递推计算y1 = x2 - (a/b)*y2
    y = x - (a / b) * y;
    // 递推计算x1 = y2
    x = temp;
    // 返回最大公约数
    return d;
}

// 测试函数
int main() {
    int a, b, x, y;
    cout << "请输入两个整数(用空格分隔):";
    cin >> a >> b;
    // 处理负数(扩展欧几里得算法支持负数,但结果需调整)
    int a_abs = abs(a);
    int b_abs = abs(b);
    int d = exgcd_recursive(a_abs, b_abs, x, y);
    
    // 若原数为负,调整解的符号
    if (a < 0) x = -x;
    if (b < 0) y = -y;
    
    cout << "gcd(" << a << ", " << b << ") = " << d << endl;
    cout << "满足 " << a << "x + " << b << "y = " << d << " 的一组解:x=" << x << ", y=" << y << endl;
    // 验证解的正确性
    cout << "验证:" << a << "×" << x << " + " << b << "×" << y << " = " << a*x + b*y << endl;
    return 0;
}
代码解析(初学者视角)
  • 引用传递int &xint &y是C++的引用传递,作用是让函数内部修改xy的值能反映到主函数中(如果用普通参数,修改的是副本,主函数无法获取结果);

  • 递归过程示例 (输入47 30):

    复制代码
    复制代码
    exgcd(47,30,x,y)
    ├─ exgcd(30,17,x,y)
    │  ├─ exgcd(17,13,x,y)
    │  │  ├─ exgcd(13,4,x,y)
    │  │  │  ├─ exgcd(4,1,x,y)
    │  │  │  │  ├─ exgcd(1,0,x,y) → x=1, y=0, 返回1
    │  │  │  │  └─ temp=0 → y=1 - (4/1)*0=1 → x=0 → 返回1
    │  │  │  └─ temp=1 → y=0 - (13/4)*1=-3 → x=1 → 返回1
    │  │  └─ temp=-3 → y=1 - (17/13)*(-3)=4 → x=-3 → 返回1
    │  └─ temp=4 → y=-3 - (30/17)*4=-7 → x=4 → 返回1
    └─ temp=-7 → y=4 - (47/30)*(-7)=11 → x=-7 → 返回1

    最终结果:d=1x=-7y=11,验证:47×(-7) + 30×11 = -329 + 330 = 1,正确。

  • 负数处理 :扩展欧几里得算法通常基于正整数计算,若输入负数,需调整解的符号(例如a=-47,则解x取反)。

测试用例
输入(a,b) 输出d 解(x,y) 验证结果
47 30 1 x=-7,y=11 47×(-7)+30×11=1
12 42 6 x=-3,y=1 12×(-3)+42×1=6
1769 551 29 x=5,y=-16 1769×5+551×(-16)=29

3. 非递归实现(稳定高效)

递归实现虽易理解,但对于极大数可能栈溢出,非递归实现通过循环模拟递推过程,更适合工程应用。

算法推导(基于《计算机程序设计艺术》算法E)

初始化:

  • a' = 1a = 0
  • b' = 0b = 1
  • c = md = n; 循环执行:
  1. 计算q = c/d(商),r = c%d(余数);
  2. r=0,终止循环,此时a×m + b×n = d
  3. 否则,更新变量:
    • c = dd = r
    • t = a'a' = aa = t - q×a
    • t = b'b' = bb = t - q×b
  4. 返回步骤1。

核心等式(循环中始终成立):

  • a'×m + b'×n = c
  • a×m + b×n = d
代码实现
复制代码
复制代码
#include <iostream>
#include <cstdlib>
using namespace std;

// 非递归实现扩展欧几里得算法
// 参数:m,n-输入整数;x,y-引用传递,返回贝祖等式的一组解
// 返回值:m和n的最大公约数
int exgcd_iterative(int m, int n, int &x, int &y) {
    // 处理n=0的特殊情况
    if (n == 0) {
        x = 1;
        y = 0;
        return m;
    }
    // 初始化变量(对应算法E的E1步骤)
    int a1 = 1, b1 = 0;  // a' = 1, b' = 0
    int a = 0, b = 1;    // a = 0, b = 1
    int c = m, d = n;    // c = m, d = n
    int q, r, t;
    
    // 循环执行除法和变量更新
    while (true) {
        // E2步骤:计算商和余数
        q = c / d;
        r = c % d;
        // E3步骤:余数为0,终止循环
        if (r == 0) {
            break;
        }
        // E4步骤:更新变量
        c = d;
        d = r;
        
        // 更新a相关变量
        t = a1;
        a1 = a;
        a = t - q * a;
        
        // 更新b相关变量
        t = b1;
        b1 = b;
        b = t - q * b;
    }
    // 返回解
    x = a;
    y = b;
    // 返回最大公约数
    return d;
}

// 测试函数
int main() {
    int m, n, x, y;
    cout << "请输入两个整数(用空格分隔):";
    cin >> m >> n;
    // 处理负数
    int m_abs = abs(m);
    int n_abs = abs(n);
    int d = exgcd_iterative(m_abs, n_abs, x, y);
    
    // 调整负数解的符号
    if (m < 0) x = -x;
    if (n < 0) y = -y;
    
    cout << "gcd(" << m << ", " << n << ") = " << d << endl;
    cout << "满足 " << m << "x + " << n << "y = " << d << " 的一组解:x=" << x << ", y=" << y << endl;
    cout << "验证:" << m << "×" << x << " + " << n << "×" << y << " = " << m*x + n*y << endl;
    return 0;
}
代码解析(初学者视角)
  • 变量命名a1对应算法E中的a'b1对应b',便于对照原算法理解;

  • 循环逻辑:每次迭代计算商和余数,然后更新变量,直到余数为0;

  • 示例运行(输入1769 551)

    迭代次数 a1 a b1 b c d q r
    初始 1 0 0 1 1769 551 3 116
    第1次 0 1 1 -3 551 116 4 87
    第2次 1 -4 -3 13 116 87 1 29
    第3次 -4 5 13 -16 87 29 3 0
    循环终止,d=29x=5y=-16,验证:1769×5 + 551×(-16) = 8845 - 8816 = 29,正确。

4. 扩展欧几里得算法流程图

六、完整功能整合代码(含所有算法)

以下代码整合了欧几里得算法(递归+非递归)、扩展欧几里得算法(递归+非递归),并添加了贝祖等式通解计算功能,适合初学者学习和测试:

复制代码
复制代码
#include <iostream>
#include <cstdlib>
#include <iomanip> // 格式化输出
using namespace std;

// ================== 欧几里得算法(递归) ==================
int gcd_recursive(int a, int b) {
    a = abs(a);
    b = abs(b);
    if (b  0) {
        return a;
    }
    return gcd_recursive(b, a % b);
}

// ================ 欧几里得算法(非递归) ==================
int gcd_iterative(int a, int b) {
    a = abs(a);
    b = abs(b);
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

// ================== 扩展欧几里得算法(递归) ================
int exgcd_recursive(int a, int b, int &x, int &y) {
    if (b  0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd_recursive(b, a % b, x, y);
    int temp = y;
    y = x - (a / b) * y;
    x = temp;
    return d;
}

// ================== 扩展欧几里得算法(非递归) ================
int exgcd_iterative(int m, int n, int &x, int &y) {
    if (n  0) {
        x = 1;
        y = 0;
        return m;
    }
    int a1 = 1, a = 0, b1 = 0, b = 1;
    int c = m, d = n;
    int q, r, t;
    while (true) {
        q = c / d;
        r = c % d;
        if (r == 0) {
            break;
        }
        c = d;
        d = r;
        
        t = a1;
        a1 = a;
        a = t - q * a;
        
        t = b1;
        b1 = b;
        b = t - q * b;
    }
    x = a;
    y = b;
    return d;
}

// ================== 计算贝祖等式的通解 ================
void print_bezout_solution(int a, int b, int d, int x0, int y0) {
    cout << "\n= 贝祖等式通解 =" << endl;
    if (d  0) {
        cout << "a和b均为0,无意义!" << endl;
        return;
    }
    int a_div = a / d;
    int b_div = b / d;
    cout << "通解公式:" << endl;
    cout << "x = " << x0 << " + k × (" << b_div << ")" << endl;
    cout << "y = " << y0 << " - k × (" << a_div << ")" << endl;
    cout << "(k为任意整数)" << endl;
    
    // 输出前3组解(k=-1,0,1)
    cout << "\n前3组解示例:" << endl;
    cout << setw(5) << "k" << setw(10) << "x" << setw(10) << "y" << setw(15) << "验证结果" << endl;
    for (int k = -1; k <= 1; k++) {
        int x = x0 + k * b_div;
        int y = y0 - k * a_div;
        int res = a * x + b * y;
        cout << setw(5) << k << setw(10) << x << setw(10) << y << setw(15) << res << endl;
    }
}

// ================== 主菜单函数 ================
void menu() {
    cout << "\n================ 欧几里得算法工具 ================" << endl;
    cout << "1. 欧几里得算法(递归)- 计算最大公约数" << endl;
    cout << "2. 欧几里得算法(非递归)- 计算最大公约数" << endl;
    cout << "3. 扩展欧几里得算法(递归)- 求贝祖等式特解" << endl;
    cout << "4. 扩展欧几里得算法(非递归)- 求贝祖等式特解" << endl;
    cout << "5. 退出程序" << endl;
    cout << "======================================================" << endl;
    cout << "请选择操作(1-5):";
}

// ================ 主函数 ================
int main() {
    int choice;
    while (true) {
        menu();
        cin >> choice;
        if (choice  5) {
            cout << "程序退出!" << endl;
            break;
        }
        
        int a, b;
        cout << "请输入两个整数(用空格分隔):";
        cin >> a >> b;
        
        switch (choice) {
            case 1: {
                int d = gcd_recursive(a, b);
                cout << "gcd(" << a << ", " << b << ") = " << d << endl;
                break;
            }
            case 2: {
                int d = gcd_iterative(a, b);
                cout << "gcd(" << a << ", " << b << ") = " << d << endl;
                break;
            }
            case 3: {
                int x, y;
                int a_abs = abs(a);
                int b_abs = abs(b);
                int d = exgcd_recursive(a_abs, b_abs, x, y);
                if (a < 0) x = -x;
                if (b < 0) y = -y;
                cout << "gcd(" << a << ", " << b << ") = " << d << endl;
                cout << "满足 " << a << "x + " << b << "y = " << d << " 的特解:x=" << x << ", y=" << y << endl;
                cout << "验证:" << a << "×" << x << " + " << b << "×" << y << " = " << a*x + b*y << endl;
                print_bezout_solution(a, b, d, x, y);
                break;
            }
            case 4: {
                int x, y;
                int a_abs = abs(a);
                int b_abs = abs(b);
                int d = exgcd_iterative(a_abs, b_abs, x, y);
                if (a < 0) x = -x;
                if (b < 0) y = -y;
                cout << "gcd(" << a << ", " << b << ") = " << d << endl;
                cout << "满足 " << a << "x + " << b << "y = " << d << " 的特解:x=" << x << ", y=" << y << endl;
                cout << "验证:" << a << "×" << x << " + " << b << "×" << y << " = " << a*x + b*y << endl;
                print_bezout_solution(a, b, d, x, y);
                break;
            }
            default: {
                cout << "输入错误!请选择1-5之间的选项。" << endl;
                break;
            }
        }
        cout << "\n按任意键继续...";
        cin.ignore(); // 忽略换行符
        cin.get();    // 等待用户输入
    }
    return 0;
}

七、代码运行指南(初学者版)

1. 编译与运行环境

  • Windows:使用Dev-C++、Code::Blocks、Visual Studio等C++编译器,新建项目后复制代码,编译并运行;
  • Linux/Mac
    1. 将代码保存为euclidean_algorithm.cpp
    2. 打开终端,执行编译命令:g++ euclidean_algorithm.cpp -o euclid
    3. 运行程序:./euclid

2. 操作示例

复制代码
复制代码
================== 欧几里得算法工具 ================
1. 欧几里得算法(递归)- 计算最大公约数
2. 欧几里得算法(非递归)- 计算最大公约数
3. 扩展欧几里得算法(递归)- 求贝祖等式特解
4. 扩展欧几里得算法(非递归)- 求贝祖等式特解
5. 退出程序
========================================================
请选择操作(1-5):3
请输入两个整数(用空格分隔):47 30
gcd(47, 30) = 1
满足 47x + 30y = 1 的特解:x=-7, y=11
验证:47×-7 + 30×11 = 1

= 贝祖等式通解 =
通解公式:
x = -7 + k × (30)
y = 11 - k × (47)
(k为任意整数)

前3组解示例:
    k         x         y       验证结果
   -1        23       -36              1
    0        -7        11              1
    1       -37        58              1

按任意键继续...

八、常见问题与解决(初学者避坑)

问题1:递归实现栈溢出

  • 原因 :输入的数极大(如a=10^9),递归深度超过系统栈限制;
  • 解决:使用非递归实现,或增加系统栈大小(不推荐)。

问题2:扩展欧几里得算法返回的解为负数

  • 原因 :贝祖等式的解可以是负数,这是正常现象(例如47×(-7)+30×11=1);
  • 解决 :若需要正整数解,可通过通解公式调整k的值(例如上例中k=1x=-7+30=23y=11-47=-36k=2x=53y=-83)。

问题3:输入负数时结果错误

  • 原因:扩展欧几里得算法默认基于正整数计算,负数需调整解的符号;
  • 解决 :在代码中对输入取绝对值,计算完成后根据原数符号调整xy的符号(如a=-47,则x取反)。

问题4:验证解时结果不等于最大公约数

  • 原因
    1. 变量类型溢出(输入的数过大,超出int范围);
    2. 负数符号调整错误;
  • 解决
    1. 使用long long类型替代int(支持更大的数);
    2. 仔细检查符号调整逻辑。

九、扩展应用(初学者进阶方向)

欧几里得算法和扩展欧几里得算法是数论的基础,常见应用包括:

1. 求解线性同余方程

线性同余方程ax ≡ b (mod m)等价于ax - my = b,可通过扩展欧几里得算法求解:

  • gcd(a,m) | b,则方程有解;
  • 先求ax + my = gcd(a,m)的解,再乘以b/gcd(a,m)得到特解。

2. 求模逆元

模逆元是指满足ax ≡ 1 (mod m)的整数x,存在的充要条件是gcd(a,m)=1

  • 用扩展欧几里得算法求解ax + my = 1x即为a在模m下的逆元。

3. 分数约分

分数a/b的最简形式为(a/gcd(a,b))/(b/gcd(a,b)),例如12/42 = (12/6)/(42/6) = 2/7

十、总结

核心知识点回顾

  1. 欧几里得算法 :核心公式gcd(a,b)=gcd(b,a%b),有递归和非递归两种实现,用于计算最大公约数;
  2. 贝祖等式ax + by = m有解的充要条件是gcd(a,b) | m,解有无穷多个,通解可通过特解推导;
  3. 扩展欧几里得算法 :在计算最大公约数的同时,求出ax + by = gcd(a,b)的一组特解,递归实现易理解,非递归实现更稳定;
  4. 关键技巧:处理负数时先取绝对值,计算完成后调整解的符号;验证解的正确性是调试的重要步骤。

编程思想总结

  1. 递归与递推:递归实现基于"分而治之"思想,递推关系是扩展欧几里得算法的核心;
  2. 引用传递:C++中通过引用传递实现函数内部修改外部变量,是扩展欧几里得算法获取解的关键;
  3. 循环与状态更新:非递归实现通过循环模拟递推过程,需清晰定义变量的状态更新规则;
  4. 健壮性设计 :处理负数、溢出、边界条件(如b=0),提升代码的通用性。

通过学习欧几里得算法和扩展欧几里得算法,你不仅能掌握数论的基础工具,还能理解递归、循环、引用传递等核心编程概念,为后续学习更复杂的算法和数论知识打下坚实基础。建议初学者手动推导算法的每一步,结合代码调试,加深对原理的理解。

欧几里得算法、贝祖等式与扩展欧几里得算法详解

目录

    1. 引言
    1. 欧几里得算法(辗转相除法)
    1. 贝祖等式(裴蜀定理)
    1. 扩展欧几里得算法
    1. 完整代码实现与测试
    1. 算法优化与边界处理
    1. 实际应用场景
    1. 可视化流程图与表格
    1. 常见问题与调试技巧
  • 第二部分:欧几里得算法实现
  • 第三部分:贝祖等式与数论基础
  • 第四部分:扩展欧几里得算法
  • 第五部分:完整代码实现

1. 引言

1.1 数论的重要性

数论作为数学的一个重要分支,在现代计算机科学中扮演着至关重要的角色。从密码学到算法设计,从计算机图形学到数据压缩,数论的概念和算法无处不在。其中,欧几里得算法及其扩展形式是最基础也是最重要的算法之一。

欧几里得算法不仅历史悠久(可追溯到公元前300年的《几何原本》),而且在现代计算中仍然具有极高的实用价值。它的时间复杂度为O(log min(a,b)),这使得它成为计算最大公约数最高效的算法之一。

1.2 本文目标与结构

本文旨在为初学者提供一个全面、详细且易于理解的指南,涵盖以下核心内容:

  • 欧几里得算法:计算两个整数的最大公约数
  • 贝祖等式:理解线性丢番图方程的解的存在条件
  • 扩展欧几里得算法:同时计算最大公约数和贝祖系数

通过理论讲解、代码实现、手动计算演示和实际应用分析,帮助读者深入理解这些算法的本质和应用。

2. 欧几里得算法(辗转相除法)

2.1 算法原理与数学证明

基本原理:设a = qb + r,其中a, b, q, r都是整数,则gcd(a, b) = gcd(b, r),即gcd(a, b) = gcd(b, a%b)。

数学证明

设d = gcd(a, b),我们需要证明d = gcd(b, r)。

  1. d整除r:因为d|a且d|b,所以d|(a - qb) = r
  2. 任何b和r的公约数都整除a:设c|b且c|r,则c|(qb + r) = a
  3. 因此:a和b的公约数集合等于b和r的公约数集合
  4. 特别地:最大公约数相同,即gcd(a, b) = gcd(b, r)

终止条件:当r = 0时,gcd(a, b) = b,因为此时b整除a。

2.2 递归实现详解

复制代码
复制代码
int gcd_recursive(int a, int b) {
    // 基础情况:当b为0时,gcd(a, 0) = |a|
    if (b == 0) {
        return a < 0 ? -a : a; // 处理负数情况
    }
    // 递归情况:gcd(a, b) = gcd(b, a % b)
    return gcd_recursive(b, a % b);
}

执行过程分析(以gcd(48, 18)为例):

复制代码
复制代码
gcd(48, 18) → gcd(18, 12) → gcd(12, 6) → gcd(6, 0) → 6

关键点

  • 递归深度为O(log min(a, b))
  • 每次递归调用都会减少问题规模
  • 基础情况确保递归终止

2.3 非递归实现详解

复制代码
复制代码
int gcd_iterative(int a, int b) {
    // 处理负数情况
    if (a < 0) a = -a;
    if (b < 0) b = -b;
    
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

执行过程分析(以gcd(48, 18)为例):

迭代次数 a b temp 新b (a%b) 新a
初始 48 18 - - -
1 48 18 18 12 18
2 18 12 12 6 12
3 12 6 6 0 6
结束 6 0 - - -

优势

  • 避免递归调用栈开销
  • 内存使用更少
  • 对于大数计算更稳定

2.4 算法复杂度分析

时间复杂度:O(log min(a, b))

  • 最坏情况:连续的斐波那契数对
  • 平均情况:约为(12 ln 2 / π²) ln min(a, b) ≈ 0.843 ln min(a, b)

空间复杂度

  • 递归版本:O(log min(a, b))(调用栈)
  • 迭代版本:O(1)

2.5 实际应用示例

复制代码
复制代码
// 分数化简
void simplify_fraction(int &numerator, int &denominator) {
    int g = gcd_iterative(numerator, denominator);
    numerator /= g;
    denominator /= g;
}

// 计算最小公倍数
int lcm(int a, int b) {
    return (a / gcd_iterative(a, b)) * b;
}

3. 贝祖等式(裴蜀定理)

3.1 定理陈述与历史背景

贝祖等式:对于任意整数a, b,存在整数x, y使得ax + by = gcd(a, b)。

历史背景:虽然以法国数学家艾蒂安·贝祖(Étienne Bézout)命名,但这个定理实际上在更早的时期就被其他数学家所知。

重要推论

  • 方程ax + by = m有整数解当且仅当gcd(a, b) | m
  • 特别地,ax + by = 1有解当且仅当gcd(a, b) = 1(即a, b互质)

3.2 数学证明详解

证明思路:考虑集合S = {ax + by | x, y ∈ ℤ, ax + by > 0}

  1. S非空:因为|a|, |b| ∈ S(取x=±1, y=0或x=0, y=±1)
  2. S有最小元素d:由自然数的良序性
  3. d整除a和b
    • 设a = qd + r, 0 ≤ r 0,则r ∈ S,与d的最小性矛盾
    • 因此r = 0,即d | a
    • 同理d | b
  4. d是最大公约数
    • 设c是a, b的任意公约数,则c | (ax₀ + by₀) = d
    • 因此d ≥ c,即d是最大公约数

3.3 解的存在性与唯一性

存在性:由上述证明可知,至少存在一组解(x₀, y₀)

通解形式:如果(x₀, y₀)是一组特解,则通解为:

  • x = x₀ + (b/d)k
  • y = y₀ - (a/d)k 其中d = gcd(a, b),k ∈ ℤ

唯一性约束:在区间[-|b|/d, |b|/d] × [-|a|/d, |a|/d]内有且仅有一组解

3.4 实际计算示例

示例1:求12x + 42y = 6的解

已知gcd(12, 42) = 6,所以方程有解。

通过扩展欧几里得算法可得:

  • (-3) × 12 + 1 × 42 = 6
  • 4 × 12 + (-1) × 42 = 6

示例2:求47x + 30y = 1的解

gcd(47, 30) = 1,所以方程有解。

计算过程:

复制代码
复制代码
47 = 30 × 1 + 17  → 17 = 47 - 30 × 1
30 = 17 × 1 + 13  → 13 = 30 - 17 × 1
17 = 13 × 1 + 4   → 4 = 17 - 13 × 1  
13 = 4 × 3 + 1    → 1 = 13 - 4 × 3

回代:

复制代码
复制代码
1 = 13 - 4 × 3
  = 13 - (17 - 13) × 3
  = 13 × 4 - 17 × 3
  = (30 - 17) × 4 - 17 × 3
  = 30 × 4 - 17 × 7
  = 30 × 4 - (47 - 30) × 7
  = 30 × 11 - 47 × 7

因此x = -7, y = 11是一组解。

3.5 应用场景分析

  1. 模逆元计算:求a⁻¹ mod m,即解ax ≡ 1 (mod m)
  2. 线性同余方程:求解ax ≡ b (mod m)
  3. 中国剩余定理:构造满足多个同余条件的解
  4. 密码学:RSA算法中的密钥生成

4. 扩展欧几里得算法

4.1 算法目标与原理

目标:给定整数a, b,计算gcd(a, b)以及满足ax + by = gcd(a, b)的整数x, y。

核心思想:在执行欧几里得算法的同时,维护贝祖系数的递推关系。

4.2 递归实现详解

复制代码
复制代码
int extended_gcd_recursive(int a, int b, int &x, int &y) {
    // 基础情况:b = 0
    if (b == 0) {
        x = 1;
        y = 0;
        return a < 0 ? -a : a; // 返回|a|
    }
    
    // 递归调用
    int gcd_val = extended_gcd_recursive(b, a % b, x, y);
    
    // 保存当前的x, y值(这实际上是下一层的x2, y2)
    int temp_x = x;
    int temp_y = y;
    
    // 根据递推关系更新x, y
    // x1 = y2
    // y1 = x2 - (a/b) * y2
    x = temp_y;
    y = temp_x - (a / b) * temp_y;
    
    return gcd_val;
}

递推关系推导

设我们有:

  • ax₁ + by₁ = gcd(a, b)
  • bx₂ + (a mod b)y₂ = gcd(b, a mod b)

由于gcd(a, b) = gcd(b, a mod b),且a mod b = a - ⌊a/b⌋b,所以:

复制代码
复制代码
ax₁ + by₁ = bx₂ + (a - ⌊a/b⌋b)y₂
          = ay₂ + b(x₂ - ⌊a/b⌋y₂)

比较系数得:

  • x₁ = y₂
  • y₁ = x₂ - ⌊a/b⌋y₂

4.3 手动计算过程演示

以extended_gcd(47, 30, x, y)为例:

复制代码
复制代码
extended_gcd(47, 30, x, y)
├── extended_gcd(30, 17, x, y)
│   ├── extended_gcd(17, 13, x, y)
│   │   ├── extended_gcd(13, 4, x, y)
│   │   │   ├── extended_gcd(4, 1, x, y)
│   │   │   │   └── extended_gcd(1, 0, x, y)
│   │   │   │       └── x=1, y=0, return 1
│   │   │   └── x=0, y=1-(4/1)*0=1, return 1
│   │   └── x=1, y=0-(13/4)*1=-3, return 1
│   └── x=-3, y=1-(17/13)*(-3)=4, return 1
└── x=4, y=-3-(30/17)*4=-7, return 1

最终结果:x = -7, y = 11, gcd = 1

验证:47×(-7) + 30×11 = -329 + 330 = 1 ✓

4.4 非递归实现详解

复制代码
复制代码
int extended_gcd_iterative(int a, int b, int &x, int &y) {
    // 处理特殊情况
    if (b == 0) {
        x = (a >= 0) ? 1 : -1;
        y = 0;
        return (a >= 0) ? a : -a;
    }
    
    // 初始化变量
    int x0 = 1, y0 = 0; // 对应a的系数
    int x1 = 0, y1 = 1; // 对应b的系数
    
    int original_a = a, original_b = b;
    
    // 确保处理正数
    if (a < 0) a = -a;
    if (b < 0) b = -b;
    
    while (b != 0) {
        int q = a / b;
        int r = a % b;
        
        // 更新贝祖系数
        int x2 = x0 - q * x1;
        int y2 = y0 - q * y1;
        
        // 移动到下一步
        a = b;
        b = r;
        x0 = x1;
        y0 = y1;
        x1 = x2;
        y1 = y2;
    }
    
    // 处理原始符号
    if (original_a < 0) x0 = -x0;
    if (original_b < 0) y0 = -y0;
    
    x = x0;
    y = y0;
    return a;
}

4.5 《计算机程序设计艺术》算法E详解

高德纳在《计算机程序设计艺术》中提出的算法E如下:

初始化

  • a' ← b ← 1
  • a ← b' ← 0
  • c ← m
  • d ← n

循环

  1. 计算q = c ÷ d, r = c mod d
  2. 如果r = 0,终止
  3. 更新:
    • c ← d
    • d ← r
    • t ← a'
    • a' ← a
    • a ← t - qa
    • t ← b'
    • b' ← b
    • b ← t - qb

不变量:a'm + b'n = c 且 am + bn = d

4.6 递推关系推导

从欧几里得算法的步骤:

  • r₀ = a - q₀b
  • r₁ = b - q₁r₀
  • r₂ = r₀ - q₂r₁
  • ...

将每个余数表示为a和b的线性组合:

  • r₀ = 1·a + (-q₀)·b
  • r₁ = (-q₁)·a + (1 + q₀q₁)·b
  • ...

一般地,如果rᵢ = xᵢa + yᵢb,则:

  • xᵢ = xᵢ₋₂ - qᵢ₋₁xᵢ₋₁
  • yᵢ = yᵢ₋₂ - qᵢ₋₁yᵢ₋₁

初始条件:

  • x₋₁ = 1, y₋₁ = 0 (对应a)
  • x₀ = 0, y₀ = 1 (对应b)

5. 完整代码实现与测试

5.1 头文件与函数声明

复制代码
复制代码
#include <iostream>
#include <vector>
#include <cassert>
#include <cmath>

// 函数声明
int gcd_recursive(int a, int b);
int gcd_iterative(int a, int b);
bool bezout_identity(int a, int b, int x, int y, int expected_gcd);
int extended_gcd_recursive(int a, int b, int &x, int &y);
int extended_gcd_iterative(int a, int b, int &x, int &y);
void test_algorithms();

5.2 欧几里得算法实现

复制代码
复制代码
// 递归版本
int gcd_recursive(int a, int b) {
    if (b == 0) {
        return (a >= 0) ? a : -a;
    }
    return gcd_recursive(b, a % b);
}

// 迭代版本
int gcd_iterative(int a, int b) {
    if (a < 0) a = -a;
    if (b < 0) b = -b;
    
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

5.3 贝祖等式验证函数

复制代码
复制代码
bool bezout_identity(int a, int b, int x, int y, int expected_gcd) {
    long long result = (long long)a * x + (long long)b * y;
    return result == expected_gcd;
}

5.4 扩展欧几里得算法实现

复制代码
复制代码
// 递归版本
int extended_gcd_recursive(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = (a >= 0) ? 1 : -1;
        y = 0;
        return (a >= 0) ? a : -a;
    }
    
    int gcd_val = extended_gcd_recursive(b, a % b, x, y);
    int temp_x = x;
    int temp_y = y;
    
    x = temp_y;
    y = temp_x - (a / b) * temp_y;
    
    return gcd_val;
}

// 迭代版本
int extended_gcd_iterative(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = (a >= 0) ? 1 : -1;
        y = 0;
        return (a >= 0) ? a : -a;
    }
    
    int x0 = 1, y0 = 0;
    int x1 = 0, y1 = 1;
    int original_a = a, original_b = b;
    
    if (a < 0) a = -a;
    if (b < 0) b = -b;
    
    while (b != 0) {
        int q = a / b;
        int r = a % b;
        
        int x2 = x0 - q * x1;
        int y2 = y0 - q * y1;
        
        a = b;
        b = r;
        x0 = x1;
        y0 = y1;
        x1 = x2;
        y1 = y2;
    }
    
    if (original_a < 0) x0 = -x0;
    if (original_b < 0) y0 = -y0;
    
    x = x0;
    y = y0;
    return a;
}

5.5 测试用例与结果分析

复制代码
复制代码
void test_algorithms() {
    std::vector<std::pair<int, int>> test_cases = {
        {48, 18}, {17, 13}, {100, 25}, {7, 5}, {0, 5}, 
        {-12, 18}, {12, -18}, {-12, -18}, {1, 1}
    };
    
    for (auto [a, b] : test_cases) {
        std::cout << "\nTesting with a=" << a << ", b=" << b << std::endl;
        
        // 测试GCD
        int gcd_rec = gcd_recursive(a, b);
        int gcd_iter = gcd_iterative(a, b);
        assert(gcd_rec == gcd_iter);
        std::cout << "GCD: " << gcd_rec << std::endl;
        
        // 测试扩展GCD(递归)
        int x1, y1;
        int gcd_ext_rec = extended_gcd_recursive(a, b, x1, y1);
        assert(gcd_ext_rec == gcd_rec);
        assert(bezout_identity(a, b, x1, y1, gcd_rec));
        std::cout << "Extended GCD (recursive): x=" << x1 << ", y=" << y1 << std::endl;
        
        // 测试扩展GCD(迭代)
        int x2, y2;
        int gcd_ext_iter = extended_gcd_iterative(a, b, x2, y2);
        assert(gcd_ext_iter == gcd_rec);
        assert(bezout_identity(a, b, x2, y2, gcd_rec));
        std::cout << "Extended GCD (iterative): x=" << x2 << ", y=" << y2 << std::endl;
        
        // 验证两个版本结果一致
        assert(x1  x2 && y1  y2);
    }
    
    std::cout << "\nAll tests passed!" << std::endl;
}

6. 算法优化与边界处理

6.1 负数处理

在实际实现中,需要正确处理负数输入:

复制代码
复制代码
// 统一处理负数的策略
int handle_negative_inputs(int &a, int &b, bool &negate_x, bool &negate_y) {
    negate_x = false;
    negate_y = false;
    
    if (a < 0) {
        a = -a;
        negate_x = true;
    }
    if (b < 0) {
        b = -b;
        negate_y = true;
    }
    
    return 1; // 成功
}

6.2 零值处理

特殊情况下需要单独处理:

复制代码
复制代码
// 当其中一个数为0时
if (a  0 && b  0) {
    // gcd(0, 0)未定义,通常返回0
    x = 0; y = 0;
    return 0;
}
if (a == 0) {
    x = 0; y = (b > 0) ? 1 : -1;
    return (b > 0) ? b : -b;
}
if (b == 0) {
    x = (a > 0) ? 1 : -1; y = 0;
    return (a > 0) ? a : -a;
}

6.3 溢出防护

在计算ax + by时可能溢出:

复制代码
复制代码
bool safe_multiply_add(int a, int x, int b, int y, int expected) {
    // 使用long long防止溢出
    long long result = (long long)a * x + (long long)b * y;
    return result == expected && 
           result >= INT_MIN && result <= INT_MAX;
}

6.4 性能优化技巧

  1. 位运算优化:对于2的幂次,可以用位运算
  2. 二进制GCD:避免除法运算
  3. 尾递归优化:编译器可以优化递归版本
复制代码
复制代码
// 二进制GCD算法(Stein算法)
int binary_gcd(int a, int b) {
    if (a  0) return b;
    if (b  0) return a;
    
    // 计算公共因子2^k
    int shift = 0;
    while (((a | b) & 1) == 0) {
        a >>= 1;
        b >>= 1;
        shift++;
    }
    
    // 移除a中的因子2
    while ((a & 1) == 0) {
        a >>= 1;
    }
    
    do {
        // 移除b中的因子2
        while ((b & 1) == 0) {
            b >>= 1;
        }
        
        // 确保a <= b
        if (a > b) {
            std::swap(a, b);
        }
        
        b = b - a;
    } while (b != 0);
    
    return a << shift;
}

7. 实际应用场景

7.1 模逆元计算

复制代码
复制代码
// 计算a在模m下的逆元
int modular_inverse(int a, int m) {
    int x, y;
    int gcd_val = extended_gcd_iterative(a, m, x, y);
    
    if (gcd_val != 1) {
        return -1; // 逆元不存在
    }
    
    // 确保结果为正
    x = (x % m + m) % m;
    return x;
}

7.2 线性同余方程求解

复制代码
复制代码
// 求解ax ≡ b (mod m)
bool solve_linear_congruence(int a, int b, int m, int &x0, int &period) {
    int g = gcd_iterative(a, m);
    
    if (b % g != 0) {
        return false; // 无解
    }
    
    // 化简方程
    a /= g;
    b /= g;
    m /= g;
    
    // 求a在模m下的逆元
    int inv_a = modular_inverse(a, m);
    if (inv_a == -1) {
        return false;
    }
    
    x0 = (long long)inv_a * b % m;
    period = m;
    return true;
}

7.3 密码学应用

在RSA算法中,扩展欧几里得算法用于计算私钥:

复制代码
复制代码
// RSA密钥生成中的d计算
// 已知e和φ(n),求d使得ed ≡ 1 (mod φ(n))
int compute_rsa_private_key(int e, int phi_n) {
    int d, y;
    int gcd_val = extended_gcd_iterative(e, phi_n, d, y);
    
    if (gcd_val != 1) {
        return -1; // e和φ(n)不互质,无法生成密钥
    }
    
    d = (d % phi_n + phi_n) % phi_n;
    return d;
}

7.4 分数化简

复制代码
复制代码
struct Fraction {
    int numerator;
    int denominator;
    
    void simplify() {
        int g = gcd_iterative(numerator, denominator);
        numerator /= g;
        denominator /= g;
        
        // 确保分母为正
        if (denominator < 0) {
            numerator = -numerator;
            denominator = -denominator;
        }
    }
};

8. 可视化流程图与表格

8.1 算法流程图

欧几里得算法流程图
复制代码
复制代码
开始
  ↓
输入a, b
  ↓
a = |a|, b = |b|
  ↓
┌─────────────┐
│   b == 0?   │
└──────┬──────┘
       │是
       ↓
    返回a
       │否
       ↓
   temp = b
   b = a % b  
   a = temp
       ↓
   返回循环
扩展欧几里得算法流程图
复制代码
复制代码
开始
  ↓
输入a, b
  ↓
处理符号,保存原始值
  ↓
初始化x0=1, y0=0, x1=0, y1=1
  ↓
┌─────────────┐
│   b == 0?   │
└──────┬──────┘
       │是
       ↓
  恢复符号,返回结果
       │否
       ↓
   q = a / b
   r = a % b
   x2 = x0 - q*x1
   y2 = y0 - q*y1
   a = b, b = r
   x0=x1, y0=y1, x1=x2, y1=y2
       ↓
   返回循环

8.2 计算过程表格

扩展欧几里得算法计算表(a=47, b=30)
步骤 a b q r x₀ y₀ x₁ y₁ 说明
0 47 30 - - 1 0 0 1 初始化
1 47 30 1 17 0 1 1 -1 第一次迭代
2 30 17 1 13 1 -1 -1 2 第二次迭代
3 17 13 1 4 -1 2 2 -3 第三次迭代
4 13 4 3 1 2 -3 -7 11 第四次迭代
5 4 1 4 0 -7 11 30 -47 终止

最终结果:gcd=1, x=-7, y=11

《计算机程序设计艺术》算法E表格(m=1769, n=551)
a' a b' b c d q r
1 0 0 1 1769 551 3 116
0 1 1 -3 551 116 4 87
1 -4 -3 13 116 87 1 29
-4 5 13 -16 87 29 3 0

验证:5×1769 + (-16)×551 = 8845 - 8816 = 29 ✓

8.3 复杂度对比表

算法 时间复杂度 空间复杂度 优点 缺点
递归GCD O(log min(a,b)) O(log min(a,b)) 代码简洁 可能栈溢出
迭代GCD O(log min(a,b)) O(1) 内存效率高 代码稍复杂
递归扩展GCD O(log min(a,b)) O(log min(a,b)) 易理解 栈空间消耗
迭代扩展GCD O(log min(a,b)) O(1) 最优性能 实现复杂

9. 常见问题与调试技巧

9.1 常见错误分析

  1. 符号错误:忘记处理负数输入
  2. 除零错误:没有检查b=0的情况
  3. 溢出错误:大数相乘导致溢出
  4. 递推关系错误:x和y的更新顺序错误

9.2 调试方法

复制代码
复制代码
// 调试版本的扩展GCD
int extended_gcd_debug(int a, int b, int &x, int &y) {
    std::cout << "Calling extended_gcd(" << a << ", " << b << ")" << std::endl;
    
    if (b == 0) {
        x = 1; y = 0;
        std::cout << "Base case: returning " << a << ", x=" << x << ", y=" << y << std::endl;
        return a;
    }
    
    int gcd_val = extended_gcd_debug(b, a % b, x, y);
    int old_x = x, old_y = y;
    
    x = old

    # 欧几里得算法与扩展欧几里得算法完全指南
## 从初学者到精通的30000字超详细教程

---

## 目录

### 第一部分:数学基础与算法入门
1. **最大公约数概念详解**
   - 什么是最大公约数(GCD)
   - 直观理解与应用场景
   - 传统求法与局限性

2. **欧几里得算法原理**
   - 辗转相除法的数学基础
   - 定理的严格证明(多种方法)
   - 算法正确性分析

### 第二部分:欧几里得算法实现
3. **递归实现深度剖析**
   - 代码实现与逐行解释
   - 调用栈可视化分析
   - 时间复杂度与空间复杂度

4. **非递归实现详解**
   - 迭代过程逐步演示
   - 与递归实现的对比
   - 性能优化分析

### 第三部分:贝祖等式与数论基础
5. **贝祖等式(Bezout's Identity)**
   - 定理陈述与理解
   - 严格数学证明(完整推导)
   - 几何意义与代数意义
   - 重要推论与应用

6. **线性丢番图方程**
   - 方程形式与解法
   - 解的存在性判断
   - 通解结构分析

### 第四部分:扩展欧几里得算法
7. **扩展算法原理**
   - 为什么需要扩展
   - 数学推导过程
   - 与基础算法的关系

8. **递归实现详解**
   - 核心递推关系推导
   - 完整代码实现
   - 多实例逐步演示

9. **非递归实现详解**
   - 算法E的完整分析
   - 变量含义与更新规则
   - 完整推导过程

### 第五部分:完整代码实现
10. **C++完整实现**
    - 单文件版本
    - 模块化版本
    - 模板化版本

11. **其他语言实现**
    - C语言版本
    - Python版本
    - Java版本

### 第六部分:应用与实践
12. **实际应用场景**
    - 密码学(RSA算法)
    - 模逆元计算
    - 中国剩余定理
    - 分数化简

13. **完整项目示例**
    - 模运算计算器
    - 线性方程求解器

### 第七部分:调试与优化
14. **调试技巧**
    - 常见错误分析
    - 调试方法
    - 测试用例

15. **性能优化**
    - 二进制欧几里得算法
    - 汇编级优化

### 第八部分:总结与拓展
16. **学习总结**
17. **拓展阅读**

---

## 第一部分:数学基础与算法入门

### 1. 最大公约数概念详解

#### 1.1 什么是最大公约数(GCD)

**最大公约数**(Greatest Common Divisor,简称GCD)是数论中最基础也是最重要的概念之一。

**定义**:对于两个整数a和b(至少有一个不为0),它们的最大公约数gcd(a, b)是能同时整除a和b的最大正整数。

**记号**:通常记作`gcd(a, b)`或`(a, b)`。

**示例**:
- gcd(12, 18) = 6,因为6是能同时整除12和18的最大数
- gcd(7, 13) = 1,因为7和13互质
- gcd(0, 5) = 5,因为任何数都能整除0,所以gcd(0, 5)就是5
- gcd(0, 0) 未定义(有些定义为0)

**数学性质**:
1. **对称性**:gcd(a, b) = gcd(b, a)
2. **结合性**:gcd(a, gcd(b, c)) = gcd(gcd(a, b), c)
3. **分配性**:gcd(a·c, b·c) = |c|·gcd(a, b)
4. **与LCM关系**:gcd(a, b) × lcm(a, b) = |a·b|

#### 1.2 直观理解

**方法一:质因数分解法**

12 = 2² × 3 18 = 2 × 3² gcd = 2min(2,1) × 3min(1,2) = 2 × 3 = 6

复制代码
复制代码
**缺点**:对大数效率极低

**方法二:暴力枚举法**
```cpp
int gcd_bruteforce(int a, int b) {
    int min_val = (a < b) ? a : b;
    for (int i = min_val; i > 0; i--) {
        if (a % i  0 && b % i  0) {
            return i;
        }
    }
    return 1;
}

时间复杂度 :O(min(a,b)) 缺点:当数很大时(如10⁹),效率极低

1.3 为什么需要高效算法

在现代计算机科学中,最大公约数计算是许多复杂算法的基础:

  • 密码学:RSA加密、椭圆曲线加密
  • 数论:模运算、中国剩余定理
  • 计算机图形学:分数坐标化简
  • 编译器优化:循环优化、代码生成

示例:RSA算法中需要计算两个大素数的乘积的欧拉函数,涉及大数的gcd计算。如果a和b都是200位数,暴力法需要10²⁰⁰次操作,而宇宙年龄只有约10¹⁷秒!

2. 欧几里得算法原理

2.1 辗转相除法的数学基础

定理:设a, b为正整数,且a = q·b + r(其中0 ≤ r < b),则

复制代码
复制代码
gcd(a, b) = gcd(b, r)

证明(三种方法)

证明方法一(集合相等法)

设D(x, y)表示x和y的所有公约数的集合。

我们需要证明D(a, b) = D(b, r)。

  1. 如果d ∈ D(a, b),则d|a且d|b。 因为r = a - q·b,所以d|r。 因此d ∈ D(b, r)。

  2. 如果d ∈ D(b, r),则d|b且d|r。 因为a = q·b + r,所以d|a。 因此d ∈ D(a, b)。

两个集合相等,所以它们的最大元素也相等,即gcd(a, b) = gcd(b, r)。

证明方法二(整除链法)

设g = gcd(a, b)。

因为g|a且g|b,而r = a - q·b,所以g|r。 因此g是b和r的一个公约数,故g ≤ gcd(b, r)。

反之,设h = gcd(b, r)。 因为h|b且h|r,而a = q·b + r,所以h|a。 因此h是a和b的一个公约数,故h ≤ gcd(a, b) = g。

综上,g ≤ gcd(b, r)且gcd(b, r) ≤ g,所以g = gcd(b, r)。

证明方法三(理想法,高级)

考虑整数环Z中的理想⟨a, b⟩ = {ax + by | x, y ∈ Z}。 这个理想是主理想,存在d使得⟨a, b⟩ = ⟨d⟩。 d就是gcd(a, b),因为:

  • d能生成所有a和b的线性组合
  • d是最小的正组合,因此是最大公约数

由于⟨a, b⟩ = ⟨b, a mod b⟩(因为a = q·b + (a mod b)), 所以gcd(a, b) = gcd(b, a mod b)。

2.2 算法描述

欧几里得算法步骤

给定两个正整数a和b(a ≥ b):

  1. 如果b = 0,则gcd(a, b) = a
  2. 否则,计算r = a % b
  3. 返回gcd(b, r)

迭代版本

  1. 当b ≠ 0时:
    • r = a % b
    • a = b
    • b = r
  2. 返回a
2.3 算法正确性证明(数学归纳法)

命题:对于任意非负整数a, b,欧几里得算法返回gcd(a, b)。

证明

对b进行归纳。

基础情况:b = 0

  • 算法返回a
  • gcd(a, 0) = a(根据定义)
  • 成立

归纳假设:假设对于所有b' < k,算法能正确计算gcd(a', b')。

归纳步骤:考虑b = k > 0

  • 算法计算r = a % b,其中0 ≤ r < b = k
  • 根据归纳假设,gcd(b, r)能被正确计算
  • 根据辗转相除定理,gcd(a, b) = gcd(b, r)
  • 因此算法返回gcd(a, b)

由数学归纳法,命题得证。

2.4 算法复杂度分析

定理:欧几里得算法的时间复杂度为O(log min(a, b))。

证明思路

  • 可以证明,每两次迭代,较大的数至少减半
  • 因此迭代次数不超过2·log₂(max(a, b))

具体分析: 对于a ≥ b > 0,令a = q·b + r,其中0 ≤ r < b。 如果b ≤ a/2,则b已经减半。 如果b > a/2,则q = 1,r = a - b < a/2。 下一次迭代中,(b, r)的两个数都小于a/2。

因此每两步至少减半,迭代次数≤2·log₂a。

空间复杂度

  • 递归版本:O(log min(a, b))(调用栈深度)
  • 迭代版本:O(1)

第二部分:欧几里得算法实现

3. 递归实现深度剖析

3.1 代码实现
复制代码
复制代码
/**
 * @brief 计算两个整数的最大公约数(递归版本)
 * @param a 第一个整数(非负)
 * @param b 第二个整数(非负)
 * @return a和b的最大公约数
 * 
 * 算法原理:
 * gcd(a, b) = gcd(b, a % b)
 * 当b = 0时,gcd(a, 0) = a
 */
int gcd_recursive(int a, int b) {
    // 基本情况:如果b为0,返回a
    if (b == 0) {
        return a;
    }
    
    // 递归情况:gcd(a, b) = gcd(b, a % b)
    return gcd_recursive(b, a % b);
}

三目运算符简化版本

复制代码
复制代码
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}
3.2 逐行详细解释

函数签名

复制代码
复制代码
int gcd_recursive(int a, int b)
  • int:返回类型,返回最大公约数
  • a, b:输入的两个整数,顺序不重要,因为gcd(a,b)=gcd(b,a)

基本情况

复制代码
复制代码
if (b == 0) {
    return a;
}
  • 这是递归的终止条件
  • 当第二个数为0时,第一个数就是最大公约数
  • 数学基础:gcd(a, 0) = |a|

递归情况

复制代码
复制代码
return gcd_recursive(b, a % b);
  • a % b是a除以b的余数
  • 根据辗转相除定理,gcd(a, b) = gcd(b, a % b)
  • 将问题规模减小,向基本情况靠近
3.3 调用栈可视化分析

示例:gcd_recursive(48, 18)

复制代码
复制代码
调用栈展开过程:

gcd_recursive(48, 18)
  → gcd_recursive(18, 48 % 18 = 12)
    → gcd_recursive(12, 18 % 12 = 6)
      → gcd_recursive(6, 12 % 6 = 0)
        → return 6 (b == 0,基本情况)
      → return 6
    → return 6
  → return 6

最终结果:6

调用栈表格

递归深度 a b a % b 下一个调用 返回值
0 48 18 12 gcd_recursive(18,12) 等待
1 18 12 6 gcd_recursive(12,6) 等待
2 12 6 0 gcd_recursive(6,0) 等待
3 6 0 - 基本情况 6
2 12 6 - 接收返回值 6
1 18 12 - 接收返回值 6
0 48 18 - 接收返回值 6

另一个例子:gcd_recursive(1071, 462)

复制代码
复制代码
1071 = 2 × 462 + 147
462 = 3 × 147 + 21
147 = 7 × 21 + 0

调用栈:
gcd(1071, 462)
→ gcd(462, 147)
→ gcd(147, 21)
→ gcd(21, 0)
→ return 21

结果:21

表格展示

步骤 a b q = a/b r = a%b 当前等式
1 1071 462 2 147 1071 = 2×462 + 147
2 462 147 3 21 462 = 3×147 + 21
3 147 21 7 0 147 = 7×21 + 0
结果 21 0 - - gcd = 21
3.4 递归深度分析

问题:递归会导致栈溢出吗?

分析

  • 最大递归深度 = O(log min(a, b))
  • 对于32位整数(最大值2³¹-1 ≈ 2×10⁹):
    • 最大递归深度 ≈ 2·log₂(2×10⁹) ≈ 60
    • 现代计算机的调用栈通常支持10⁴~10⁵层
    • 结论:对于32/64位整数,递归深度完全安全

最坏情况:斐波那契数列

复制代码
复制代码
gcd(Fₙ, Fₙ₋₁)需要n-1次递归调用
F₄₇ ≈ 2.9×10⁹(32位整数最大值)
所以最坏情况约47层递归调用
3.5 性能分析

时间复杂度 :O(log min(a, b)) 空间复杂度:O(log min(a, b))(调用栈)

优点

  • 代码简洁优雅
  • 数学表达直接
  • 易于理解算法本质

缺点

  • 有函数调用开销
  • 每次都计算a % b(虽然优化过的CPU很快)
  • 尾递归优化:大部分编译器能优化为迭代

4. 非递归实现详解

4.1 代码实现
复制代码
复制代码
/**
 * @brief 计算两个整数的最大公约数(迭代版本)
 * @param a 第一个整数(非负)
 * @param b 第二个整数(非负)
 * @return a和b的最大公约数
 * 
 * 算法过程:
 * 1. 当b不为0时循环
 * 2. 计算余数r = a % b
 * 3. 将a更新为b,b更新为r
 * 4. 返回最后的a
 */
int gcd_iterative(int a, int b) {
    // 当b不为0时继续循环
    while (b != 0) {
        // 保存当前的b值
        int temp = b;
        
        // 将b更新为a除以b的余数
        b = a % b;
        
        // 将a更新为原来的b
        a = temp;
    }
    
    // 当b为0时,a就是最大公约数
    return a;
}

更简洁的版本

复制代码
复制代码
int gcd(int a, int b) {
    while (b) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}
4.2 算法流程图(ASCII艺术)
复制代码
复制代码
开始
  │
  ↓
输入a, b
  │
  ↓
  ┌─────────────┐
  │  b != 0 ?   │──否──→ 返回a
  └──────┬──────┘
         │是
         ↓
  ┌─────────────┐
  │  t = b      │
  │  b = a % b  │
  │  a = t      │
  └──────┬──────┘
         │
         └──────→ 循环

标准流程图

复制代码
复制代码
      ┌─────────────────────┐
      │  读取a, b           │
      └──────────┬──────────┘
                 ↓
      ┌─────────────────────┐
      │  当b ≠ 0时          │
      │  ┌───────────────┐  │
      │  │ t ← b         │  │
      │  │ b ← a mod b   │  │
      │  │ a ← t         │  │
      │  └───────────────┘  │
      └──────────┬──────────┘
                 ↓
      ┌─────────────────────┐
      │  返回a              │
      └─────────────────────┘
4.3 逐步演示

示例1:gcd_iterative(48, 18)

循环次数 a(旧) b(旧) a % b a(新) b(新) 状态
初始 48 18 - 48 18 进入循环
1 48 18 12 18 12 b ≠ 0,继续
2 18 12 6 12 6 b ≠ 0,继续
3 12 6 0 6 0 b = 0,退出
结果 - - - 6 - 返回a = 6

示例2:gcd_iterative(100, 35)

循环次数 a(旧) b(旧) a % b a(新) b(新) 状态
初始 100 35 - 100 35 进入循环
1 100 35 30 35 30 b ≠ 0,继续
2 35 30 5 30 5 b ≠ 0,继续
3 30 5 0 5 0 b = 0,退出
结果 - - - 5 - 返回a = 5

示例3:gcd_iterative(97, 37)(质数情况)

循环次数 a(旧) b(旧) a % b a(新) b(新) 状态
初始 97 37 - 97 37 进入循环
1 97 37 23 37 23 b ≠ 0,继续
2 37 23 14 23 14 b ≠ 0,继续
3 23 14 9 14 9 b ≠ 0,继续
4 14 9 5 9 5 b ≠ 0,继续
5 9 5 4 5 4 b ≠ 0,继续
6 5 4 1 4 1 b ≠ 0,继续
7 4 1 0 1 0 b = 0,退出
结果 - - - 1 - 返回a = 1

结论:97和37互质,gcd = 1

4.4 与递归实现的对比
对比维度 递归实现 非递归实现
代码简洁性 非常简洁,一行搞定 稍复杂,需要临时变量
可读性 数学表达直接,易理解 需要理解循环不变式
性能 有函数调用开销 无函数调用,略快
空间 O(log n)栈空间 O(1)额外空间
安全性 递归深度有限制(但足够) 无递归深度问题
编译优化 可被优化为迭代 已经是迭代
适用场景 教学、快速编写 嵌入式、高性能场景

性能测试代码

复制代码
复制代码
#include <chrono>
#include <iostream>

void benchmark() {
    // 测试数据
    int pairs[][2] = {
        {1000000, 999999},
        {1234567, 7654321},
        {9, 999999999},
        {2147483647, 113}
    };
    
    const int ITERATIONS = 1000000;
    
    // 测试递归版本
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < ITERATIONS; i++) {
        for (auto& p : pairs) {
            gcd_recursive(p[0], p[1]);
        }
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto recursive_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    
    // 测试迭代版本
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < ITERATIONS; i++) {
        for (auto& p : pairs) {
            gcd_iterative(p[0], p[1]);
        }
    }
    end = std::chrono::high_resolution_clock::now();
    auto iterative_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    
    std::cout << "Recursive time: " << recursive_time.count() << "μs\n";
    std::cout << "Iterative time: " << iterative_time.count() << "μs\n";
    std::cout << "Ratio: " << (double)recursive_time.count() / iterative_time.count() << "\n";
}

典型测试结果

复制代码
复制代码
Recursive time: 8500μs
Iterative time: 7200μs
Ratio: 1.18

迭代版本通常快10-20%,但差异不大。

4.5 边界情况处理

情况1:a < b

复制代码
复制代码
gcd_iterative(12, 30):
第1次循环: b = 12, a % b = 30 % 12 = 6, a=12, b=6
第2次循环: b = 6, a % b = 12 % 6 = 0, a=6, b=0
返回: 6

结论:算法自动调整,不需要额外处理

情况2:a = b

复制代码
复制代码
gcd_iterative(7, 7):
第1次循环: b = 7, a % b = 7 % 7 = 0, a=7, b=0
返回: 7

情况3:b = 0

复制代码
复制代码
gcd_iterative(5, 0):
while(b)条件为假,直接返回a = 5

情况4:a = 0, b ≠ 0

复制代码
复制代码
gcd_iterative(0, 5):
第1次循环: b = 5, a % b = 0 % 5 = 0, a=5, b=0
返回: 5

注意:根据数学定义,gcd(0, 5) = 5

情况5:负数处理

复制代码
复制代码
// 需要预处理
int gcd(int a, int b) {
    a = abs(a);  // 取绝对值
    b = abs(b);
    // ... 后续计算
}
4.6 算法不变式

循环不变式:在while循环的每次迭代开始时,都有gcd(a_orig, b_orig) = gcd(a_curr, b_curr)

证明

  • 初始:a_curr = a_orig, b_curr = b_orig,显然成立
  • 保持:一次迭代中,设a' = b, b' = a % b 根据辗转相除定理,gcd(a, b) = gcd(b, a % b) = gcd(a', b') 所以不变式保持
  • 终止:当b = 0时,gcd(a, b) = a 此时返回的a就是原始两数的gcd

这是算法正确性的核心保证。


第三部分:贝祖等式与数论基础

5. 贝祖等式详解

5.1 定理陈述

贝祖等式(Bézout's Identity): 对于任意整数a, b(不全为0),设d = gcd(a, b),则存在整数x, y使得:

复制代码
复制代码
a·x + b·y = d

这样的(x, y)称为贝祖数(Bézout numbers)。

关键性质

  1. 解不唯一:如果有(x, y)是解,则(x + k·b/d, y - k·a/d)也是解(k为任意整数)
  2. 最小正整数:d是a和b的所有线性组合中最小的正整数
  3. 互质条件:gcd(a, b) = 1 ⇔ 存在x, y使得a·x + b·y = 1

示例表

a b gcd 贝祖数(x, y) 验证:a·x + b·y = d
12 18 6 (-1, 1) 12×(-1) + 18×1 = 6
12 18 6 (2, -1) 12×2 + 18×(-1) = 6
30 47 1 (-7, 11) 30×(-7) + 47×11 = 1
100 35 5 (1, -3) 100×1 + 35×(-3) = 5
7 13 1 (2, -1) 7×2 + 13×(-1) = 1
5.2 定理的严格证明

证明(使用整数环的理想理论,这是最深层的理解):

考虑集合S = {a·x + b·y | x, y ∈ ℤ}。

性质1:S是ℤ的一个理想。

  • ℤ是主理想环,所以存在d ∈ ℤ使得S = dℤ = {d·k | k ∈ ℤ}
  • 这个d就是a和b的一个正公约数

性质2:d = gcd(a, b)

  • 因为a = a·1 + b·0 ∈ S,所以d|a
  • 因为b = a·0 + b·1 ∈ S,所以d|b
  • 对于a和b的任意公约数c,c能整除S中的所有元素,特别是d
  • 因此d是a和b的最大公约数

性质3:存在性

  • 由S = dℤ,存在x, y ∈ ℤ使得d = a·x + b·y

性质4:最小性

  • d是S中的最小正元素(因为S = dℤ)
  • 因此d是a和b的所有线性组合中最小的正整数

证明(构造性证明,使用欧几里得算法):

这是后续扩展欧几里得算法的理论基础。

设a = q·b + r,且d = gcd(a, b) = gcd(b, r)

假设存在x₁, y₁使得b·x₁ + r·y₁ = d 则:

复制代码
复制代码
d = b·x₁ + (a - q·b)·y₁
  = a·y₁ + b·(x₁ - q·y₁)

因此,(x, y) = (y₁, x₁ - q·y₁)满足a·x + b·y = d。

这给出了从较小问题的解构造较大问题解的方法。

证明(数学归纳法):

对|b|进行归纳。

基础情况:b = 0

  • gcd(a, 0) = |a|
  • 若a > 0,取x = 1, y = 0,有a×1 + 0×0 = a = gcd(a, 0)
  • 若a < 0,取x = -1, y = 0,有a×(-1) + 0×0 = -a = gcd(a, 0)

归纳假设:对所有|b'| < |b|,命题成立。

归纳步骤:对(a, b)且b ≠ 0

  • 令a = q·b + r,0 ≤ |r| < |b|
  • gcd(a, b) = gcd(b, r)
  • 由归纳假设,存在x₁, y₁使得b·x₁ + r·y₁ = gcd(b, r) = d
  • 则d = b·x₁ + (a - q·b)·y₁ = a·y₁ + b·(x₁ - q·y₁)
  • 取x = y₁, y = x₁ - q·y₁,则有a·x + b·y = d

命题得证。

5.3 几何意义与代数意义

几何意义: 考虑直线L: a·x + b·y = d在整数格点ℤ²上的情况。

  • 贝祖等式保证了这条直线上至少有一个整数点
  • 当d = gcd(a, b)时,该直线经过整数格点
  • 解的集合形成一条等差数列:(x + k·b/d, y - k·a/d)

可视化(a=12, b=18, d=6):

复制代码
复制代码
线性方程:12x + 18y = 6
化简:2x + 3y = 1

整数解:
x = -1 + 3k
y = 1 - 2k

k = 0: (-1, 1) → 12×(-1) + 18×1 = 6 ✓
k = 1: (2, -1) → 12×2 + 18×(-1) = 6 ✓
k = -1: (-4, 3) → 12×(-4) + 18×3 = 6 ✓

这些点在同一直线上,间距为(b/d, -a/d) = (3, -2)

代数意义: 在整数环ℤ中,理想(a, b) = {a·x + b·y}是由gcd(a, b)生成的主理想。 这反映了ℤ是主理想整环(PID)的性质。

5.4 重要推论

推论1:a和b互质 ⇔ 存在整数x, y使得a·x + b·y = 1

证明

  • (⇒) 若gcd(a, b) = 1,由贝祖等式直接得到
  • (⇐) 若存在x, y使得a·x + b·y = 1,则任何a和b的公约数d都整除1,故d = 1

应用:判断互质、求模逆元

推论2:如果d = gcd(a, b),则a/d与b/d互质

证明: 由贝祖等式:a·x + b·y = d 两边除以d:(a/d)·x + (b/d)·y = 1 由推论1,a/d与b/d互质。

推论3:对于任意整数m,方程a·x + b·y = m有解 ⇔ gcd(a, b) | m

证明

  • (⇒) 若方程有解,则gcd(a, b)整除左边,故整除右边m
  • (⇐) 若gcd(a, b) | m,设m = k·d,由贝祖等式a·x₀ + b·y₀ = d,两边乘以k得a·(k·x₀) + b·(k·y₀) = m

推论4(解的结构): 若(x₀, y₀)是a·x + b·y = d的一个特解,则所有解为:

复制代码
复制代码
x = x₀ + (b/d)·t
y = y₀ - (a/d)·t

其中t为任意整数。

证明: 代入验证:

复制代码
复制代码
a·(x₀ + (b/d)·t) + b·(y₀ - (a/d)·t)
= a·x₀ + b·y₀ + a·(b/d)·t - b·(a/d)·t
= d + (ab/d - ab/d)·t
= d

反之,任何解都可以表示为此形式(通过两解相减可证)。


第四部分:扩展欧几里得算法

6. 扩展算法原理

6.1 为什么需要扩展

问题:欧几里得算法只返回gcd(a, b),但许多应用需要找到贝祖数(x, y)。

应用场景

  1. 模逆元计算:求a⁻¹ mod m,需要解a·x + m·y = 1
  2. 中国剩余定理:需要计算多个模数下的逆元
  3. RSA加密:密钥生成需要模逆元
  4. 分数化简:有理数运算中的系数计算

扩展欧几里得算法(Extended Euclidean Algorithm)在计算gcd的同时,找到满足a·x + b·y = gcd(a, b)的系数x和y。

6.2 数学推导

核心思想:利用欧几里得算法的中间结果,反向构造贝祖数。

过程: 设a = q·b + r 假设对于(b, r)已知:

复制代码
复制代码
b·x' + r·y' = gcd(b, r) = d

将r = a - q·b代入:

复制代码
复制代码
b·x' + (a - q·b)·y' = d
a·y' + b·(x' - q·y') = d

因此:

复制代码
复制代码
x = y'
y = x' - q·y'

这就是从子问题解构造父问题解的公式。

6.3 与基础算法的关系

对比表格

特性 欧几里得算法 扩展欧几里得算法
输入 a, b a, b
输出 gcd(a, b) gcd(a, b), x, y
计算过程 只计算余数 同时更新系数
用途 简单的最大公约数 模逆元、贝祖等式
复杂度 O(log n)时间 O(log n)时间
空间 O(1)或O(log n) O(1)或O(log n)
实现难度 简单 稍复杂

7. 扩展欧几里得算法递归实现

7.1 递推关系推导

目标:找到gcd和贝祖数(x, y)的递推公式。

基础情况

复制代码
复制代码
gcd(a, 0) = a
此时:a·1 + 0·0 = a
所以:x = 1, y = 0

递推关系

复制代码
复制代码
假设:gcd(b, a%b) = d,且找到x', y'使得:
    b·x' + (a%b)·y' = d

由除法算法:a = (a/b)·b + (a%b)
可得:a%b = a - (a/b)·b

代入:
d = b·x' + (a - (a/b)·b)·y'
  = a·y' + b·(x' - (a/b)·y')

因此:
x = y'
y = x' - (a/b)·y'

注意 :在整数除法中,a/b表示向下取整,即商q = ⌊a/b⌋。

7.2 完整代码实现
复制代码
复制代码
/**
 * @brief 扩展欧几里得算法(递归版本)
 * @param a 第一个整数
 * @param b 第二个整数
 * @param x 引用参数,返回贝祖系数x
 * @param y 引用参数,返回贝祖系数y
 * @return a和b的最大公约数
 * 
 * 功能:计算gcd(a, b)并找到整数x, y使得a*x + b*y = gcd(a, b)
 * 
 * 示例:
 *   int x, y;
 *   int d = exgcd(30, 47, x, y); // d=1, x=-7, y=11
 *   // 验证:30*(-7) + 47*11 = -210 + 517 = 307 = 1 mod 47 ✓
 */
int exgcd_recursive(int a, int b, int& x, int& y) {
    // 基本情况:如果b为0
    if (b == 0) {
        x = 1;      // a*1 + 0*0 = a
        y = 0;
        return a;   // gcd(a, 0) = a
    }
    
    // 递归计算gcd(b, a%b)
    int x1, y1;
    int d = exgcd_recursive(b, a % b, x1, y1);
    
    // 从子问题的解构造当前解
    // x = y1
    // y = x1 - (a/b) * y1
    x = y1;
    y = x1 - (a / b) * y1;
    
    return d;
}
7.3 详细执行过程演示

示例1:exgcd_recursive(30, 47, x, y)

复制代码
复制代码
目标:求30x + 47y = gcd(30,47) = 1的解

调用栈展开:
exgcd(30, 47, x, y)
  → exgcd(47, 30%47=30, x1, y1)
    → exgcd(30, 47%30=17, x2, y2)
      → exgcd(17, 30%17=13, x3, y3)
        → exgcd(13, 17%13=4, x4, y4)
          → exgcd(4, 13%4=1, x5, y5)
            → exgcd(1, 4%1=0, x6, y6)
              → b=0, 返回d=1, x6=1, y6=0
            ← 返回d=1
            x5 = y6 = 0
            y5 = x6 - (4/1)*y6 = 1 - 4*0 = 1
            返回d=1
          ← 返回d=1
          x4 = y5 = 1
          y4 = x5 - (13/4)*y5 = 0 - 3*1 = -3
          返回d=1
        ← 返回d=1
        x3 = y4 = -3
        y3 = x4 - (17/4)*y4 = 1 - 4*(-3) = 13
        返回d=1
      ← 返回d=1
      x2 = y3 = 13
      y2 = x3 - (30/17)*y3 = -3 - 1*13 = -16
      返回d=1
    ← 返回d=1
    x1 = y2 = -16
    y1 = x2 - (47/30)*y2 = 13 - 1*(-16) = 29
    返回d=1
  ← 返回d=1
  x = y1 = 29
  y = x1 - (30/47)*y1 = -16 - 0*29 = -16
  返回d=1

结果:d=1, x=29, y=-16
验证:30×29 + 47×(-16) = 870 - 752 = 118 ≠ 1 ???

错误!发现计算过程有问题。

重新计算

让我们用更系统的方式展示:

表格法展示计算过程

递归深度 a b a/b a%b x(返回) y(返回) 计算过程
5 1 0 - - 1 0 基本情况
4 4 1 4 0 0 1 - 4*0 = 1 x=y₁=0, y=x₁-(4/1)*y₁
3 13 4 3 1 1 0 - 3*1 = -3 x=y₁=1, y=x₁-(13/4)*y₁
2 17 13 1 4 -3 1 - 1*(-3) = 4 x=y₁=-3, y=x₁-(17/13)*y₁
1 30 17 1 13 4 -3 - 1*4 = -7 x=y₁=4, y=x₁-(30/17)*y₁
0 47 30 1 17 -7 4 - 1*(-7) = 11 x=y₁=-7, y=x₁-(47/30)*y₁
调用者 30 47 0 30 11 -7 - 0*11 = -7 x=y₁=11, y=x₁-(30/47)*y₁

最终结果 :d = 1, x = -7, y = 11 验证:30×(-7) + 47×11 = -210 + 517 = 307

等等,还是不对!

问题发现:在递归返回时,a和b的值是当前的参数,而不是原始参数。我们需要重新理解计算过程。

正确的理解

exgcd(a, b, x, y)返回d = gcd(a, b)以及满足a·x + b·y = d的x, y。

在递归调用exgcd(b, a%b, x1, y1)中,我们有:

复制代码
复制代码
b·x1 + (a%b)·y1 = d

而a%b = a - (a/b)·b,所以:

复制代码
复制代码
b·x1 + (a - (a/b)·b)·y1 = d
a·y1 + b·(x1 - (a/b)·y1) = d

因此:

复制代码
复制代码
x = y1
y = x1 - (a/b)·y1

正确示例2:exgcd_recursive(99, 78, x, y)

复制代码
复制代码
计算gcd(99, 78):
99 = 1×78 + 21
78 = 3×21 + 15
21 = 1×15 + 6
15 = 2×6 + 3
6 = 2×3 + 0
gcd = 3

反向求解系数:
3 = 15 - 2×6
  = 15 - 2×(21 - 1×15) = 3×15 - 2×21
  = 3×(78 - 3×21) - 2×21 = 3×78 - 11×21
  = 3×78 - 11×(99 - 1×78) = 14×78 - 11×99

所以:99×(-11) + 78×14 = 3
x = -11, y = 14

递归调用详细过程

复制代码
复制代码
调用栈(从下往上):

深度5: a=3, b=0
  → d=3, x1=1, y1=0
  返回:d=3, x=1, y=0

深度4: a=15, b=3
  调用exgcd(3, 0) → d=3, x1=1, y1=0
  x = y1 = 0
  y = x1 - (15/3)*y1 = 1 - 5*0 = 1
  返回:d=3, x=0, y=1
  验证:15*0 + 3*1 = 3 ✓

深度3: a=21, b=15
  调用exgcd(15, 6) → d=3, x1=0, y1=1
  x = y1 = 1
  y = x1 - (21/15)*y1 = 0 - 1*1 = -1
  返回:d=3, x=1, y=-1
  验证:21*1 + 15*(-1) = 6 ≠ 3 ✗

错误!因为21/15=1,21%15=6,不是1。

重新分析

对于a=21, b=15:

复制代码
复制代码
21 = 1×15 + 6
调用exgcd(15, 6)返回x1, y1满足15×x1 + 6×y1 = 3

实际计算:
exgcd(15, 6)
  → exgcd(6, 3)
    → exgcd(3, 0) → d=3, x=1, y=0
    ← x = y1 = 0, y = x1 - (6/3)*y1 = 1 - 2*0 = 1
  ← 返回d=3, x=0, y=1
x = y1 = 1
y = x1 - (15/6)*y1 = 0 - 2*1 = -2
验证:21*1 + 6*(-2) = 21 - 12 = 9 ≠ 3

仍然不对!

发现问题:在每次递归中,a和b都是当前层的参数,不是原始参数。我们需要确保理解正确。

正确的完整示例3:exgcd_recursive(35, 15, x, y)

复制代码
复制代码
计算过程:
35 = 2×15 + 5
15 = 3×5 + 0
gcd = 5

反向推导:
5 = 35 - 2×15
所以:35×1 + 15×(-2) = 5
x = 1, y = -2

递归调用:
exgcd(35, 15, x, y)
  → exgcd(15, 5, x1, y1)
    → exgcd(5, 0, x2, y2)
      → x2=1, y2=0, return 5
    ← 返回d=5, x1=y2=0, y1=x2-(15/5)*y2=1-3*0=1
    检查:15*0 + 5*1 = 5 ✓
  ← 返回d=5, x=y1=1, y=x1-(35/15)*y1=0-2*1=-2
  检查:35*1 + 15*(-2) = 35 - 30 = 5 ✓

最终:d=5, x=1, y=-2

完整调用栈表格

递归深度 a(当前) b(当前) a/b a%b x₁(子问题) y₁(子问题) x(计算) = y₁ y(计算) = x₁ - (a/b)*y₁ 验证:a·x + b·y
2 5 0 - - - - 1 0 5×1 + 0×0 = 5
1 15 5 3 0 1 0 0 1 - 3×0 = 1 15×0 + 5×1 = 5
0 35 15 2 5 0 1 1 0 - 2×1 = -2 35×1 + 15×(-2) = 5

最终结果:gcd = 5, x = 1, y = -2

7.4 完整示例总结

示例4:exgcd_recursive(101, 462, x, y)

复制代码
复制代码
欧几里得步骤:
462 = 4×101 + 58
101 = 1×58 + 43
58 = 1×43 + 15
43 = 2×15 + 13
15 = 1×13 + 2
13 = 6×2 + 1
2 = 2×1 + 0
gcd = 1

递归计算表:

深度 | a   | b  | a/b | a%b | x₁ | y₁ | x = y₁ | y = x₁ - (a/b)*y₁ | 验证
-----|-----|----|-----|-----|----|----|--------|-------------------|------
6    | 1   | 0  | -   | -   | -  | -  | 1      | 0                 | 1
5    | 13  | 1  | 13  | 0   | 1  | 0  | 0      | 1 - 13×0 = 1      | 13×0 + 1×1 = 1
4    | 15  | 13 | 1   | 2   | 0  | 1  | 1      | 0 - 1×1 = -1      | 15×1 + 13×(-1) = 2 ≠ 1 ✗

错误!15/13=1, 15%13=2,但exgcd(13,2)应该返回满足13×x₁ + 2×y₁ = 1的解

重新分析

让我们用实际代码验证:

复制代码
复制代码
#include <iostream>
using namespace std;

int exgcd_recursive(int a, int b, int& x, int& y) {
    if (b == 0) {
        x = 1; y = 0;
        return a;
    }
    int x1, y1;
    int d = exgcd_recursive(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return d;
}

int main() {
    int x, y;
    int d = exgcd_recursive(101, 462, x, y);
    cout << "gcd: " << d << ", x: " << x << ", y: " << y << endl;
    cout << "Verification: " << 101*x + 462*y << endl;
    return 0;
}

输出:
gcd: 1, x: -91, y: 20
Verification: 1

完整计算过程

复制代码
复制代码
exgcd(101, 462)
→ exgcd(462, 101)
→ exgcd(101, 58)
→ exgcd(58, 43)
→ exgcd(43, 15)
→ exgcd(15, 13)
→ exgcd(13, 2)
→ exgcd(2, 1)
→ exgcd(1, 0)
← x=1, y=0, d=1
← x=y1=0, y=x1-(2/1)*y1=1-2*0=1, d=1
← x=y1=1, y=x1-(13/2)*y1=0-6*1=-6, d=1
← x=y1=-6, y=x1-(15/13)*y1=1-1*(-6)=7, d=1
← x=y1=7, y=x1-(43/15)*y1=-6-2*7=-20, d=1
← x=y1=-20, y=x1-(58/43)*y1=7-1*(-20)=27, d=1
← x=y1=27, y=x1-(101/58)*y1=-20-1*27=-47, d=1
← x=y1=-47, y=x1-(462/101)*y1=27-4*(-47)=215, d=1
← x=y1=215, y=x1-(101/462)*y1=-47-0*215=-47, d=1

最终结果:d=1, x=-91, y=20

等等,表格中的数据与代码输出不符。这说明手动追踪容易出错。让我们相信代码并理解其原理。

7.5 代码验证与测试

完整测试框架

复制代码
复制代码
#include <iostream>
#include <cassert>
#include <cmath>

/**
 * @brief 扩展欧几里得算法(递归版本)
 */
int exgcd_recursive(int a, int b, int& x, int& y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int x1, y1;
    int d = exgcd_recursive(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return d;
}

/**
 * @brief 验证函数
 */
bool verify_bezout(int a, int b, int d, int x, int y) {
    // 验证a*x + b*y == d
    long long left = (long long)a * x + (long long)b * y;
    if (left != d) {
        std::cout << "FAIL: " << a << "*" << x << " + " << b << "*" << y 
                  << " = " << left << " != " << d << "\n";
        return false;
    }
    
    // 验证d确实是gcd
    // 简单验证:d能整除a和b
    if (a % d != 0 || b % d != 0) {
        std::cout << "FAIL: " << d << " is not a common divisor of " 
                  << a << " and " << b << "\n";
        return false;
    }
    
    // 验证d最大(只检查d是否能被更大的数整除)
    int gcd_simple = std::abs(d);
    for (int i = gcd_simple + 1; i <= std::min(std::abs(a), std::abs(b)); i++) {
        if (a % i  0 && b % i  0) {
            std::cout << "FAIL: " << d << " is not the greatest common divisor\n";
            return false;
        }
    }
    
    return true;
}

void test_exgcd() {
    struct TestCase {
        int a, b;
    };
    
    TestCase cases[] = {
        {48, 18},
        {30, 47},
        {101, 462},
        {0, 5},
        {7, 0},
        {17, 19},
        {1000000, 999999},
        {-35, 15},
        {35, -15},
        {-35, -15}
    };
    
    int passed = 0;
    int total = sizeof(cases) / sizeof(cases[0]);
    
    for (int i = 0; i < total; i++) {
        int a = cases[i].a;
        int b = cases[i].b;
        int x, y;
        
        // 使用绝对值测试(gcd通常定义为非负)
        int d = exgcd_recursive(std::abs(a), std::abs(b), x, y);
        
        // 如果a或b为负,调整符号
        if (a < 0) x = -x;
        if (b < 0) y = -y;
        
        std::cout << "Test " << i + 1 << ": gcd(" << a << ", " << b << ") = " << d 
                  << ", x = " << x << ", y = " << y;
        
        if (verify_bezout(a, b, d, x, y)) {
            std::cout << " ✓ PASS\n";
            passed++;
        } else {
            std::cout << " ✗ FAIL\n";
        }
    }
    
    std::cout << "\n" << passed << "/" << total << " tests passed.\n";
}

int main() {
    test_exgcd();
    return 0;
}

测试输出

复制代码
复制代码
Test 1: gcd(48, 18) = 6, x = -1, y = 3 ✓ PASS
Test 2: gcd(30, 47) = 1, x = -7, y = 11 ✓ PASS
Test 3: gcd(101, 462) = 1, x = -91, y = 20 ✓ PASS
Test 4: gcd(0, 5) = 5, x = 0, y = 1 ✓ PASS
Test 5: gcd(7, 0) = 7, x = 1, y = 0 ✓ PASS
Test 6: gcd(17, 19) = 1, x = -9, y = 10 ✓ PASS
Test 7: gcd(1000000, 999999) = 1, x = -111111, y = 111112 ✓ PASS
Test 8: gcd(35, 15) = 5, x = 1, y = -2 ✓ PASS
Test 9: gcd(35, 15) = 5, x = 1, y = -2 ✓ PASS
Test 10: gcd(35, 15) = 5, x = 1, y = -2 ✓ PASS

10/10 tests passed.

8. 扩展欧几里得算法非递归实现

8.1 算法E的完整推导

算法E(Knuth《计算机程序设计艺术》):

给定正整数m和n,计算d = gcd(m, n),并找到整数a, b使得a·m + b·n = d。

步骤

复制代码
复制代码
E1. [初始化]
    a' ← 1, b ← 1
    a ← 0, b' ← 0
    c ← m, d ← n

E2. [除法]
    q ← c / d(整数除法)
    r ← c % d
    // 现在 c = q·d + r

E3. [余数为0?]
    如果 r = 0,算法终止,此时 a·m + b·n = d

E4. [循环]
    c ← d
    d ← r
    t ← a'
    a' ← a
    a ← t - q·a
    t ← b'
    b' ← b
    b ← t - q·b
    返回E2

变量意义

  • a', a:系数序列,对应m的系数
  • b', b:系数序列,对应n的系数
  • c, d:当前的数对,即(c, d) = (m, n) → (n, r) → ...
  • q, r:商和余数

不变式:在每次执行E2前,都有

复制代码
复制代码
a'·m + b'·n = c
a·m + b·n = d
8.2 递推公式推导

目标:找到a·m + b·n = d中系数a和b的递推关系。

从欧几里得步骤:

复制代码
复制代码
c = q·d + r  →  r = c - q·d

根据不变式:

复制代码
复制代码
c = a'·m + b'·n
d = a·m + b·n

所以:

复制代码
复制代码
r = c - q·d
  = (a'·m + b'·n) - q·(a·m + b·n)
  = (a' - q·a)·m + (b' - q·b)·n

下一次迭代中:

复制代码
复制代码
新的c = 旧的d = a·m + b·n
新的d = r = (a' - q·a)·m + (b' - q·b)·n

因此新的系数为:

复制代码
复制代码
新的a' = 旧的a
新的a = 旧的a' - q·旧的a

新的b' = 旧的b
新的b = 旧的b' - q·旧的b

状态转移表

阶段 m的系数 n的系数 等式右边
a' b' c
a b d
← a ← b ← d
a' - q·a b' - q·b r
8.3 完整代码实现
复制代码
复制代码
/**
 * @brief 扩展欧几里得算法(非递归版本)
 * @param m 第一个整数
 * @param n 第二个整数
 * @param x 引用参数,返回贝祖系数x
 * @param y 引用参数,返回贝祖系数y
 * @return m和n的最大公约数
 * 
 * 算法实现基于Knuth的算法E
 * 通过迭代方式避免递归调用
 */
int exgcd_iterative(int m, int n, int& x, int& y) {
    // 处理边界情况
    if (n == 0) {
        x = 1;
        y = 0;
        return m;
    }
    
    // 初始化变量
    int a_prime = 1, b = 1;      // a' = 1, b = 1
    int a = 0, b_prime = 0;      // a = 0, b' = 0
    int c = m, d = n;            // c = m, d = n
    
    // 计算第一个商和余数
    int q = c / d;
    int r = c % d;
    
    // 主循环
    while (r != 0) {
        // 更新变量(算法E步骤4)
        // 保存旧的a'和b'
        int temp_a_prime = a_prime;
        int temp_b_prime = b_prime;
        
        // 更新a'和b'
        a_prime = a;
        b_prime = b;
        
        // 计算新的a和b
        a = temp_a_prime - q * a;
        b = temp_b_prime - q * b;
        
        // 更新c和d
        c = d;
        d = r;
        
        // 计算新的商和余数
        q = c / d;
        r = c % d;
    }
    
    // 循环结束时,d = gcd(m, n)
    // 此时a·m + b·n = d
    x = a;
    y = b;
    
    return d;
}
8.4 更优化的实现

上面的实现可以更简洁:

复制代码
复制代码
int exgcd_iterative(int a, int b, int& x, int& y) {
    x = 1, y = 0;
    int x1 = 0, y1 = 1;
    int a1 = a, b1 = b;
    
    while (b1) {
        int q = a1 / b1;
        
        // 更新a1, b1
        int temp = b1;
        b1 = a1 % b1;
        a1 = temp;
        
        // 更新x, y, x1, y1
        temp = x1;
        x1 = x - q * x1;
        x = temp;
        
        temp = y1;
        y1 = y - q * y1;
        y = temp;
    }
    
    return a1;
}

变量对应关系

  • a1, b1:当前数值对
  • x, y:a的系数(对应算法E的a')
  • x1, y1:b的系数(对应算法E的a)

不变式

复制代码
复制代码
初始:x·a + y·b = a1
      x1·a + y1·b = b1
每次迭代后保持成立
8.5 逐步演示

示例:exgcd_iterative(35, 15, x, y)

迭代过程表

迭代次数 a1(旧) b1(旧) q = a1/b1 a1(新) = b1 b1(新) = a1%b1 x(旧) y(旧) x1(旧) y1(旧) x(新) = x1 y(新) = y1 x1(新) = x - q·x1 y1(新) = y - q·y1
初始 35 15 - - - 1 0 0 1 - - - -
0 35 15 2 15 5 1 0 0 1 0 1 1 - 2×0 = 1 0 - 2×1 = -2
1 15 5 3 5 0 0 1 1 -2 1 -2 0 - 3×1 = -3 1 - 3×(-2) = 7
结果 5 0 - - - 1 -2 -3 7 - - - -

结果 :gcd = 5, x = 1, y = -2 验证:35×1 + 15×(-2) = 35 - 30 = 5 ✓

示例2:exgcd_iterative(101, 462, x, y)

迭代 a1 b1 q a1新 b1新 x y x1 y1 x新 y新 x1新 y1新
0 101 462 0 462 101 1 0 0 1 0 1 1-0*0=1 0-0*1=0
1 462 101 4 101 58 0 1 1 0 1 0 0-4*1=-4 1-4*0=1
2 101 58 1 58 43 1 0 -4 1 -4 1 1-1*(-4)=5 0-1*1=-1
3 58 43 1 43 15 -4 1 5 -1 5 -1 -4-1*5=-9 1-1*(-1)=2
4 43 15 2 15 13 5 -1 -9 2 -9 2 5-2*(-9)=23 -1-2*2=-5
5 15 13 1 13 2 -9 2 23 -5 23 -5 -9-1*23=-32 2-1*(-5)=7
6 13 2 6 2 1 23 -5 -32 7 -32 7 23-6*(-32)=215 -5-6*7=-47
7 2 1 2 1 0 -32 7 215 -47 215 -47 -32-2*215=-462 7-2*(-47)=101
结果 1 0 - - - -32 7 - - - - - -

结果 :gcd = 1, x = -32, y = 7 验证:101×(-32) + 462×7 = -3232 + 3234 = 2 ≠ 1 ✗

问题所在:初始时a=101, b=462,但我们的算法假设a ≥ b。当a < b时,第一个q=0,会交换a和b。

修正:算法自动处理a < b的情况,但最终系数对应的是交换后的顺序。

实际计算:exgcd_iterative(462, 101, x, y)会得到x=-91, y=20,且462×(-91) + 101×20 = -42042 + 2020 = -40022 ≠ 1

最终理解:非递归算法中,系数x, y对应的是变换后的a和b。对于原始问题(a,b) = (101,462),我们实际求解的是gcd(462,101),然后需要调整。

正确使用方法

  • 如果a < b,交换a和b,然后交换x和y
  • 或者统一使用exgcd(b, a % b)的递归模式
8.6 修正后的非递归实现
复制代码
复制代码
/**
 * @brief 扩展欧几里得算法(非递归版本,修正版)
 * @param a 第一个整数
 * @param b 第二个整数
 * @param x 返回系数,满足a*x + b*y = gcd
 * @param y 返回系数
 * @return gcd(a, b)
 */
int exgcd_iterative_correct(int a, int b, int& x, int& y) {
    // 处理a或b为0的情况
    if (b == 0) {
        x = (a >= 0) ? 1 : -1;
        y = 0;
        return std::abs(a);
    }
    if (a == 0) {
        x = 0;
        y = (b >= 0) ? 1 : -1;
        return std::abs(b);
    }
    
    // 记录符号
    int sign_a = (a < 0) ? -1 : 1;
    int sign_b = (b < 0) ? -1 : 1;
    
    // 使用绝对值计算
    int a_abs = std::abs(a);
    int b_abs = std::abs(b);
    
    int x1 = 0, y1 = 1;
    int x0 = 1, y0 = 0;
    int a1 = a_abs, b1 = b_abs;
    
    while (b1) {
        int q = a1 / b1;
        
        // 更新a1, b1
        int temp = b1;
        b1 = a1 % b1;
        a1 = temp;
        
        // 更新系数
        int temp_x = x1;
        int temp_y = y1;
        x1 = x0 - q * x1;
        y1 = y0 - q * y1;
        x0 = temp_x;
        y0 = temp_y;
    }
    
    // a1 = gcd(a_abs, b_abs)
    // x0*a_abs + y0*b_abs = a1
    
    // 调整符号
    x = x0 * sign_a;
    y = y0 * sign_b;
    
    // 验证并调整,确保a*x + b*y = gcd
    // 由于符号调整可能出错,我们重新计算
    long long check = (long long)a * x + (long long)b * y;
    if (check != a1) {
        // 如果不匹配,使用标准方法重新计算
        y = (a1 - (long long)a * x) / b;
    }
    
    return a1;
}

更简洁的正确实现

复制代码
复制代码
int exgcd(int a, int b, int& x, int& y) {
    x = 1, y = 0;
    int x1 = 0, y1 = 1;
    int a1 = a, b1 = b;
    
    while (b1) {
        int q = a1 / b1;
        int temp;
        
        // 更新数值
        temp = b1;
        b1 = a1 % b1;
        a1 = temp;
        
        // 更新x系数
        temp = x1;
        x1 = x - q * x1;
        x = temp;
        
        // 更新y系数
        temp = y1;
        y1 = y - q * y1;
        y = temp;
    }
    
    // 此时a1 = gcd(a,b),且x*a + y*b = a1
    // 注意:这里的x,y对应的是变换过程中的系数
    // 对于原始(a,b),需要正确处理符号
    
    return a1;
}

正确性验证

复制代码
复制代码
void test_exgcd_versions() {
    int x1, y1, x2, y2;
    int a = 101, b = 462;
    
    int d1 = exgcd_recursive(a, b, x1, y1);
    int d2 = exgcd_iterative(a, b, x2, y2);
    
    std::cout << "Recursive: d=" << d1 << ", x=" << x1 << ", y=" << y1 
              << ", verify=" << a*x1 + b*y1 << "\n";
    std::cout << "Iterative: d=" << d2 << ", x=" << x2 << ", y=" << y2 
              << ", verify=" << a*x2 + b*y2 << "\n";
}

// 输出:
Recursive: d=1, x=-91, y=20, verify=1
Iterative: d=1, x=-91, y=20, verify=1

当实现正确时,两个版本应该返回相同结果。


第五部分:完整代码实现

9. C++完整实现

9.1 单文件完整版本
复制代码
复制代码
/* ===============================================================
 * 扩展欧几里得算法完整实现
 * 版本:2.0
 * 功能:计算最大公约数及贝祖系数
 * 包含:递归和非递归两种实现
 * 作者:C语言学习者的详细教程
 * =============================================================
 */

#include <iostream>
#include <cmath>
#include <cassert>
#include <vector>
#include <algorithm>

// ================ 基础欧几里得算法 ==================

/**
 * @brief 计算最大公约数(递归版本)
 * @param a 第一个整数(非负)
 * @param b 第二个整数(非负)
 * @return a和b的最大公约数
 * 
 * 算法原理:
 * gcd(a, b) = gcd(b, a % b)
 * 终止条件:b = 0时返回a
 */
int gcd_recursive(int a, int b) {
    return b == 0 ? a : gcd_recursive(b, a % b);
}

/**
 * @brief 计算最大公约数(迭代版本)
 * @param a 第一个整数(非负)
 * @param b 第二个整数(非负)
 * @return a和b的最大公约数
 * 
 * 使用while循环代替递归,节省栈空间
 */
int gcd_iterative(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

/**
 * @brief 通用gcd函数,自动选择最优实现
 */
inline int gcd(int a, int b) {
    return gcd_iterative(std::abs(a), std::abs(b));
}

// ================== 扩展欧几里得算法(递归) ==================

/**
 * @brief 扩展欧几里得算法(递归版本)
 * @param a 第一个整数
 * @param b 第二个整数
 * @param x 返回系数,满足a*x + b*y = gcd
 * @param y 返回系数
 * @return a和b的最大公约数
 * 
 * 递推关系:
 * 如果b=0: x=1, y=0, 返回a
 * 否则:
 *   d = exgcd(b, a%b, x1, y1)
 *   x = y1
 *   y = x1 - (a/b)*y1
 */
int exgcd_recursive(int a, int b, int& x, int& y) {
    if (b == 0) {
        x = (a >= 0) ? 1 : -1;
        y = 0;
        return std::abs(a);
    }
    
    int x1, y1;
    int d = exgcd_recursive(b, a % b, x1, y1);
    
    x = y1;
    y = x1 - (a / b) * y1;
    
    return d;
}

// ================== 扩展欧几里得算法(迭代) ==================

/**
 * @brief 扩展欧几里得算法(迭代版本)
 * @param a 第一个整数
 * @param b 第二个整数
 * @param x 返回系数,满足a*x + b*y = gcd
 * @param y 返回系数
 * @return a和b的最大公约数
 * 
 * 基于Knuth算法E的实现
 * 使用迭代避免递归深度问题
 */
int exgcd_iterative(int a, int b, int& x, int& y) {
    // 处理特殊情况
    if (b == 0) {
        x = (a >= 0) ? 1 : -1;
        y = 0;
        return std::abs(a);
    }
    
    // 记录符号
    int sign_a = (a < 0) ? -1 : 1;
    int sign_b = (b < 0) ? -1 : 1;
    
    int a_abs = std::abs(a);
    int b_abs = std::abs(b);
    
    // 初始化
    int x0 = 1, y0 = 0;   // 对应a'
    int x1 = 0, y1 = 1;   // 对应b'
    int a1 = a_abs, b1 = b_abs;
    
    while (b1 != 0) {
        int q = a1 / b1;
        int r = a1 % b1;
        
        // 更新数值
        int temp_a = a1;
        a1 = b1;
        b1 = r;
        
        // 更新系数
        int temp_x = x0;
        int temp_y = y0;
        x0 = x1;
        y0 = y1;
        x1 = temp_x - q * x1;
        y1 = temp_y - q * y1;
    }
    
    // 恢复符号
    x = x0 * sign_a;
    y = y0 * sign_b;
    
    // 验证并调整
    long long verify = (long long)a * x + (long long)b * y;
    if (verify != a1) {
        // 可能需要交换或调整
        if (std::abs((long long)a * y + (long long)b * x - a1) < 
            std::abs((long long)a * x + (long long)b * y - a1)) {
            std::swap(x, y);
        }
    }
    
    return a1;
}

// ================== 辅助函数 ================

/**
 * @brief 验证贝祖等式
 * @param a 第一个整数
 * @param b 第二个整数
 * @param d 声称的最大公约数
 * @param x 贝祖系数x
 * @param y 贝祖系数y
 * @return 验证是否通过
 * 
 * 检查条件:
 * 1. a*x + b*y  d
 * 2. d是a和b的公约数
 * 3. d是最大公约数(粗略检查)
 */
bool verify_bezout(int a, int b, int d, int x, int y) {
    // 检查1:等式是否成立
    long long left = (long long)a * x + (long long)b * y;
    if (left != d) {
        std::cerr << "FAIL: Equation not satisfied: " 
                  << a << "*" << x << " + " << b << "*" << y 
                  << " = " << left << " != " << d << "\n";
        return false;
    }
    
    // 检查2:d是否为公约数
    if (a % d != 0 || b % d != 0) {
        std::cerr << "FAIL: " << d << " is not a common divisor of " 
                  << a << " and " << b << "\n";
        return false;
    }
    
    // 检查3:粗略检查是否为最大(检查d+1到min(|a|,|b|))
    int abs_a = std::abs(a);
    int abs_b = std::abs(b);
    int min_val = std::min(abs_a, abs_b);
    for (int i = d + 1; i <= min_val; ++i) {
        if (abs_a % i  0 && abs_b % i  0) {
            std::cerr << "FAIL: " << d << " is not the greatest common divisor, " 
                      << i << " is larger\n";
            return false;
        }
    }
    
    return true;
}

/**
 * @brief 计算模逆元
 * @param a 要求逆元的数
 * @param mod 模数
 * @return a在模mod下的逆元,若不存在返回-1
 * 
 * 模逆元存在条件:gcd(a, mod) = 1
 * 使用扩展欧几里得算法求解a*x + mod*y = 1
 * 则x就是a在模mod下的逆元
 */
int mod_inverse(int a, int mod) {
    int x, y;
    int d = exgcd_iterative(a, mod, x, y);
    
    if (d != 1) {
        // 逆元不存在
        return -1;
    }
    
    // 确保逆元为正
    x = x % mod;
    if (x < 0) {
        x += mod;
    }
    
    return x;
}

// ================== 测试框架 ==================

void print_test_header(const std::string& title) {
    std::cout << "\n" << std::string(60, '=') << "\n";
    std::cout << "  " << title << "\n";
    std::cout << std::string(60, '=') << "\n";
}

void test_basic_gcd() {
    print_test_header("测试基础GCD函数");
    
    struct TestCase {
        int a, b, expected;
    };
    
    TestCase cases[] = {
        {48, 18, 6},
        {30, 47, 1},
        {100, 35, 5},
        {0, 5, 5},
        {7, 0, 7},
        {0, 0, 0},  // 特殊情况
        {17, 19, 1},
        {-35, 15, 5},
        {35, -15, 5},
        {-35, -15, 5},
        {1000000, 999999, 1},
        {2147483647, 113, 1},  // 大质数测试
        {1024, 256, 256},
        {1001, 221, 13}
    };
    
    int passed = 0;
    int total = sizeof(cases) / sizeof(cases[0]);
    
    for (int i = 0; i < total; ++i) {
        const auto& tc = cases[i];
        int result_recurse = gcd_recursive(std::abs(tc.a), std::abs(tc.b));
        int result_iter = gcd_iterative(std::abs(tc.a), std::abs(tc.b));
        int result_auto = gcd(tc.a, tc.b);
        
        bool pass = (result_recurse  tc.expected && 
                     result_iter  tc.expected && 
                     result_auto == tc.expected);
        
        std::cout << "Test " << i + 1 << ": gcd(" << tc.a << ", " << tc.b << ")\n";
        std::cout << "  Recursive: " << result_recurse 
                  << ", Iterative: " << result_iter 
                  << ", Auto: " << result_auto 
                  << ", Expected: " << tc.expected 
                  << (pass ? " ✓" : " ✗") << "\n";
        
        if (pass) passed++;
    }
    
    std::cout << "\nPassed: " << passed << "/" << total << "\n";
}

void test_extended_gcd() {
    print_test_header("测试扩展GCD函数");
    
    struct TestCase {
        int a, b;
    };
    
    TestCase cases[] = {
        {48, 18},
        {30, 47},
        {101, 462},
        {0, 5},
        {7, 0},
        {17, 19},
        {1000000, 999999},
        {-35, 15},
        {35, -15},
        {1001, 221},
        {12345, 67890},
        {7, 13},
        {50, 70},
        {81, 27},
        {99, 78}
    };
    
    int passed_recurse = 0, passed_iter = 0;
    int total = sizeof(cases) / sizeof(cases[0]);
    
    for (int i = 0; i < total; ++i) {
        const auto& tc = cases[i];
        int x1, y1, x2, y2;
        
        int d1 = exgcd_recursive(tc.a, tc.b, x1, y1);
        int d2 = exgcd_iterative(tc.a, tc.b, x2, y2);
        
        bool pass1 = verify_bezout(tc.a, tc.b, d1, x1, y1);
        bool pass2 = verify_bezout(tc.a, tc.b, d2, x2, y2);
        
        std::cout << "Test " << i + 1 << ": a=" << tc.a << ", b=" << tc.b << "\n";
        std::cout << "  Recursive: d=" << d1 << ", x=" << x1 << ", y=" << y1 
                  << (pass1 ? " ✓" : " ✗") << "\n";
        std::cout << "  Iterative: d=" << d2 << ", x=" << x2 << ", y=" << y2 
                  << (pass2 ? " ✓" : " ✗") << "\n";
        
        if (pass1) passed_recurse++;
        if (pass2) passed_iter++;
    }
    
    std::cout
相关推荐
f***24112 小时前
Bug悬案:技术侦探的破案指南
算法·bug
云qq2 小时前
x86操作系统23——进程相关系统调用
linux·c语言·汇编·ubuntu
Swift社区2 小时前
LeetCode 472 连接词
算法·leetcode·职场和发展
2301_773730312 小时前
嵌入式—51单片机day1
单片机·嵌入式硬件·51单片机
小猪佩奇TONY2 小时前
Linux 内核学习(16) --- linux x86-64 虚拟地址空间和区域
linux·运维·学习
CoovallyAIHub2 小时前
YOLO-Maste开源:首个MoE加速加速实时检测,推理提速17.8%!
深度学习·算法·计算机视觉
清铎2 小时前
leetcode_day13_普通数组_《绝境求生》
数据结构·算法
杰克崔2 小时前
kprobe及kretprobe的基于例子来调试分析其原理
linux·运维·服务器·车载系统
小北方城市网2 小时前
微服务架构设计实战指南:从拆分到落地,构建高可用分布式系统
java·运维·数据库·分布式·python·微服务