跳转至

数论基础(五)数论算法

章节概述

本章将介绍数论中的重要算法,包括质因数分解、勒让德符号的计算以及离散对数问题的求解。这些算法在密码学、计算机科学和算法竞赛中都有广泛应用。

学习目标

  • 掌握质因数分解的各种算法
  • 理解勒让德符号的概念和计算方法
  • 了解离散对数问题
  • 掌握婴儿-巨步算法求解离散对数
  • 了解 Pollard's Rho 算法(大数分解)
  • 掌握数论算法的优化技巧

(一)质因数分解

质因数分解的定义

将一个正整数表示为质数的乘积:

n = p₁^a₁ × p₂^a₂ × ... × pₖ^aₖ

其中 p₁ < p₂ < ... < pₖ 是质数,aᵢ 是正整数。

算法一:试除法

原理:依次尝试用质数去除 n

def prime_factorization_trial(n):
    """
    质因数分解(试除法)

    参数:
        n: 正整数

    返回:
        dict: 质因数及其指数的字典

    时间复杂度: O(√n)

    示例:
        >>> prime_factorization_trial(12)
        {2: 2, 3: 1}
        >>> prime_factorization_trial(100)
        {2: 2, 5: 2}
        >>> prime_factorization_trial(17)
        {17: 1}
    """
    factors = {}

    # 处理 2 的因子
    count = 0
    while n % 2 == 0:
        count += 1
        n = n // 2
    if count > 0:
        factors[2] = count

    # 处理奇数因子
    for i in range(3, int(n**0.5) + 1, 2):
        count = 0
        while n % i == 0:
            count += 1
            n = n // i
        if count > 0:
            factors[i] = count

    # 如果剩下的 n 是质数
    if n > 1:
        factors[n] = 1

    return factors

算法二:优化试除法

优化点: - 先筛出 √n 以内的质数 - 只用质数试除

def prime_factorization_optimized(n):
    """
    质因数分解(优化试除法)

    参数:
        n: 正整数

    返回:
        dict: 质因数及其指数的字典

    时间复杂度: O(√n / log n)
    """
    factors = {}

    # 筛出 √n 以内的质数
    limit = int(n**0.5) + 1
    primes = sieve_of_eratosthenes(limit)

    # 用质数试除
    for p in primes:
        if p * p > n:
            break
        count = 0
        while n % p == 0:
            count += 1
            n = n // p
        if count > 0:
            factors[p] = count

    # 如果剩下的 n 是质数
    if n > 1:
        factors[n] = 1

    return factors

算法三:Pollard's Rho 算法(大数分解)

应用场景:分解非常大的数(试除法太慢)

原理:利用随机化和费马小定理

import random

def pollard_rho(n):
    """
    Pollard's Rho 算法:分解大数

    参数:
        n: 待分解的合数

    返回:
        int: n 的一个非平凡因子

    时间复杂度: O(n^(1/4))
    """
    if n % 2 == 0:
        return 2

    if n % 3 == 0:
        return 3

    while True:
        c = random.randint(2, n - 1)
        f = lambda x: (pow(x, 2, n) + c) % n
        x, y, d = 2, 2, 1

        while d == 1:
            x = f(x)
            y = f(f(y))
            d = gcd(abs(x - y), n)

        if d != n:
            return d

def prime_factorization_pollard(n):
    """
    使用 Pollard's Rho 算法进行质因数分解

    参数:
        n: 正整数

    返回:
        dict: 质因数及其指数的字典

    时间复杂度: 优于试除法,特别是对大数
    """
    factors = {}

    def factorize(n):
        if n == 1:
            return
        if is_prime(n):
            factors[n] = factors.get(n, 0) + 1
            return

        d = pollard_rho(n)
        factorize(d)
        factorize(n // d)

    factorize(n)
    return factors

质因数分解的应用

应用1:计算约数个数

def count_divisors_from_factors(factors):
    """
    根据质因数分解计算约数个数

    参数:
        factors: 质因数分解结果

    返回:
        int: 约数的个数
    """
    count = 1
    for exp in factors.values():
        count *= (exp + 1)
    return count

应用2:计算约数之和

def sum_divisors_from_factors(factors):
    """
    根据质因数分解计算所有约数的和

    参数:
        factors: 质因数分解结果

    返回:
        int: 所有约数的和
    """
    total = 1
    for p, exp in factors.items():
        total *= (p**(exp + 1) - 1) // (p - 1)
    return total

(二)勒让德符号

勒让德符号的定义

对于奇质数 p 和整数 a,勒让德符号 (a/p) 定义为: - (a/p) = 0,如果 a ≡ 0 (mod p) - (a/p) = 1,如果 a 是模 p 的二次剩余 - (a/p) = -1,如果 a 是模 p 的二次非剩余

欧拉判别法

定理:对于奇质数 p 和不被 p 整除的整数 a:

(a/p) ≡ a^((p-1)/2) (mod p)

def legendre_symbol_euler(a, p):
    """
    使用欧拉判别法计算勒让德符号 (a/p)

    参数:
        a: 整数
        p: 奇质数

    返回:
        int: 0, 1, 或 -1

    时间复杂度: O(log p)

    示例:
        >>> legendre_symbol_euler(2, 7)
        1
        >>> legendre_symbol_euler(3, 7)
        -1
        >>> legendre_symbol_euler(7, 7)
        0
    """
    a = a % p
    if a == 0:
        return 0

    result = mod_pow(a, (p - 1) // 2, p)
    if result == p - 1:
        return -1
    return result

勒让德符号的性质

性质1:周期性

(a/p) = (a mod p)/p

性质2:乘法性

(ab/p) = (a/p) × (b/p)

性质3:二次互反律

如果 p 和 q 都是奇质数,则:

(p/q) × (q/p) = (-1)^((p-1)(q-1)/4)

性质4:特殊值

(-1/p) = (-1)^((p-1)/2)
(2/p) = (-1)^((p²-1)/8)

勒让德符号的应用

应用1:判断二次剩余

def is_quadratic_residue(a, p):
    """
    判断 a 是否是模 p 的二次剩余

    参数:
        a: 整数
        p: 奇质数

    返回:
        bool: 如果是二次剩余返回 True
    """
    return legendre_symbol_euler(a, p) == 1

应用2:求解二次同余方程

参见第三章的 Tonelli-Shanks 算法。

(三)离散对数

离散对数问题的定义

给定质数 p、原根 g 和整数 y,求 x 使得:

g^x ≡ y (mod p)

称 x 为 y 以 g 为底的离散对数,记作 x = log_g(y) mod p。

离散对数问题的困难性

离散对数问题被认为是计算上困难的,这是许多密码学算法(如 Diffie-Hellman 密钥交换、ElGamal 加密)安全性的基础。

婴儿-巨步算法(Baby-Step Giant-Step)

原理:将指数 x 分解为 x = im + j,其中 m = ⌈√p⌉

步骤: 1. 选择 m = ⌈√p⌉ 2. 婴儿步:计算并存储 g^j (mod p),j = 0, 1, ..., m-1 3. 巨步:计算 y × (g^(-m))^i (mod p),i = 0, 1, ..., m-1 4. 如果找到匹配,则 x = im + j

import math

def baby_step_giant_step(g, y, p):
    """
    婴儿-巨步算法求解离散对数

    求 x 使得 g^x ≡ y (mod p)

    参数:
        g: 原根
        y: 整数
        p: 质数

    返回:
        int: 离散对数 x
             如果无解返回 None

    时间复杂度: O(√p)
    空间复杂度: O(√p)

    示例:
        # 2^x ≡ 3 (mod 13)
        >>> baby_step_giant_step(2, 3, 13)
        4  # 因为 2^4 = 16 ≡ 3 (mod 13)
    """
    m = int(math.ceil(math.sqrt(p - 1)))

    # 婴儿步:计算 g^j (mod p),j ∈ [0, m-1]
    table = {}
    current = 1
    for j in range(m):
        if current not in table:
            table[current] = j
        current = (current * g) % p

    # 巨步:计算 y * g^(-im) (mod p),i ∈ [0, m-1]
    inv_g_m = mod_inverse(mod_pow(g, m, p), p)
    current = y

    for i in range(m):
        if current in table:
            x = i * m + table[current]
            if mod_pow(g, x, p) == y:
                return x
        current = (current * inv_g_m) % p

    return None  # 无解

Pohlig-Hellman 算法

适用场景:当 p-1 的质因数分解已知时

原理:利用中国剩余定理将离散对数问题分解为多个小规模的子问题

def pohlig_hellman(g, y, p):
    """
    Pohlig-Hellman 算法求解离散对数

    参数:
        g: 原根
        y: 整数
        p: 质数

    返回:
        int: 离散对数 x

    时间复杂度: O(Σ(eᵢ × log pᵢ)),其中 p-1 = Π(pᵢ^eᵢ)
    """
    # 分解 p-1
    factors = prime_factorization(p - 1)

    # 对每个质因数求解
    equations = []
    for q, e in factors.items():
        x_mod_qe = 0
        g_power = mod_pow(g, (p - 1) // q, p)
        y_power = y

        for i in range(e):
            # 求解 g^((p-1)/q) * x_i ≡ y^(something) (mod p)
            temp = mod_pow(y_power, (p - 1) // (q ** (i + 1)), p)
            x_i = baby_step_giant_step(g_power, temp, p)
            x_mod_qe += x_i * (q ** i)

            y_power = (y_power * mod_inverse(mod_pow(g, x_i * (q ** i), p), p)) % p

        equations.append((x_mod_qe % (q ** e), q ** e))

    # 使用中国剩余定理合并
    solution, _ = crt(equations)
    return solution

(四)其他重要算法

算法一:米勒-拉宾素性测试

应用场景:快速判断大数是否为质数(概率性算法)

def miller_rabin(n, k=5):
    """
    米勒-拉宾素性测试

    参数:
        n: 待测试的数
        k: 测试次数(影响准确性)

    返回:
        bool: 如果 n 可能是质数返回 True
              如果 n 肯定是合数返回 False

    时间复杂度: O(k × log³ n)

    准确性:错误概率 ≤ 4^(-k)
    """
    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0:
        return False

    # 将 n-1 分解为 d × 2^s
    d = n - 1
    s = 0
    while d % 2 == 0:
        d //= 2
        s += 1

    # 进行 k 次测试
    for _ in range(k):
        a = random.randint(2, n - 2)
        x = mod_pow(a, d, n)

        if x == 1 or x == n - 1:
            continue

        for __ in range(s - 1):
            x = mod_pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False  # 肯定是合数

    return True  # 可能是质数

算法二:扩展欧几里得算法(回顾)

已经在第二章详细介绍,这里不再赘述。

(五)实战应用

应用1:RSA 密钥生成

def generate_rsa_keypair(p, q):
    """
    生成 RSA 密钥对

    参数:
        p, q: 两个大质数

    返回:
        tuple: (公钥, 私钥)
    """
    n = p * q
    phi = (p - 1) * (q - 1)

    # 选择 e
    e = 65537  # 常用值
    while gcd(e, phi) != 1:
        e = random.randrange(2, phi)

    # 计算 d
    d = mod_inverse(e, phi)

    # 公钥 (e, n),私钥 (d, n)
    return ((e, n), (d, n))

应用2:Diffie-Hellman 密钥交换

def diffie_hellman_key_exchange(p, g):
    """
    Diffie-Hellman 密钥交换协议

    参数:
        p: 大质数
        g: 原根

    返回:
        tuple: (Alice的密钥, Bob的密钥, 共享密钥)
    """
    # Alice 选择私钥
    a = random.randint(2, p - 2)
    A = mod_pow(g, a, p)  # Alice 的公钥

    # Bob 选择私钥
    b = random.randint(2, p - 2)
    B = mod_pow(g, b, p)  # Bob 的公钥

    # 计算共享密钥
    alice_shared = mod_pow(B, a, p)
    bob_shared = mod_pow(A, b, p)

    # 验证密钥相同
    assert alice_shared == bob_shared

    return (a, b, alice_shared)

实战练习

练习1:分解大数

任务:使用 Pollard's Rho 算法分解一个大数

def exercise_1():
    """
    分解大数 1234567890123456789012345678901234567890
    """
    n = 1234567890123456789012345678901234567890
    factors = prime_factorization_pollard(n)
    print(f"{n} 的质因数分解:")
    for p, exp in sorted(factors.items()):
        print(f"  {p}^{exp}")

练习2:求解离散对数

任务:求解 2^x ≡ 5 (mod 101)

def exercise_2():
    """
    求解 2^x ≡ 5 (mod 101)
    """
    g, y, p = 2, 5, 101
    x = baby_step_giant_step(g, y, p)
    print(f"离散对数:{x}")
    print(f"验证:{g}^{x} mod {p} = {mod_pow(g, x, p)}")

练习3:判断质数

任务:使用米勒-拉宾测试判断大数是否为质数

def exercise_3():
    """
    判断大数是否为质数
    """
    numbers = [
        2**61 - 1,  # 梅森质数
        2**67 - 1,  # 非质数
        10**100 + 267,  # 未知
    ]

    for n in numbers:
        is_prime_prob = miller_rabin(n, k=10)
        print(f"{n} 可能是质数:{is_prime_prob}")

章节小结

本章介绍了重要的数论算法:

  1. 质因数分解:掌握试除法、优化试除法和 Pollard's Rho 算法
  2. 勒让德符号:理解定义、性质和计算方法
  3. 离散对数:理解离散对数问题,掌握婴儿-巨步算法
  4. 米勒-拉宾测试:掌握概率性素性测试方法
  5. 密码学应用:了解 RSA 和 Diffie-Hellman 的数学基础

这些算法是现代密码学和计算机安全的基础。

下一步

继续学习第六章:应用实例,了解数论在实际问题中的应用。


重点提醒: - Pollard's Rho 算法适合分解大数 - 婴儿-巨步算法的时间复杂度是 O(√p) - 米勒-拉宾测试是概率性算法,需要进行多次测试 - 离散对数问题是密码学的基础