算法知识图谱总览 / 数论高级族 / Miller–Rabin 素性测试

Miller–Rabin 素性测试

P1advancedstandardG-C-9

What · 是什么

概率性素性测试 O(k log n),费马小定理+二次探测定理,可确定性判定 2^64 内整数

Miller–Rabin 素性测试用于判定大整数是否为素数。将费马小定理 a^(n-1)≡1 的指数 n-1 分解为 u·2^t,结合二次探测定理(奇素数 p 下 x²≡1 的解只有 ±1)检查:若对基底 a 有 a^u≡1 或某个 a^(u·2^i)≡-1 (mod n),则通过该轮。随机 k 个基底,合数通过所有轮的概率 ≤ 4^(-k)。关键结论:对 64 位整数,取基底 {2,325,9375,28178,450775,9780504,1795265022} 是【确定性的】;对 32 位取 {2,7,61} 或前 4 个素数即可。单轮 O(log n) 次模乘。它是 Pollard-Rho 分解大整数时的前置素性判定,Python 中 pow(a, e, n) 内建快速模幂,大整数优势明显。

Why · 为什么

直觉 · Intuition

费马小定理说素数 n 对任何 a 都有 a^(n-1)≡1。反过来‘a^(n-1)≡1 推出 n 素数’并不成立(Carmichael 数会全骗过)。Miller–Rabin 的加强:把指数一路‘剥因子 2’再开平方回查——素数在平方根回路上必然撞见 ±1,而合数常在某个平方根处‘露馅’。就好像检查一扇门:所有素数门都能正常打开,但每道‘二次探测锁’多拦一道,伪造者(强伪素数)很难每一把锁都配齐钥匙。

原理 · Principle

设 n-1=u·2^t,u 奇。检查基底 a:x₀=a^u mod n;若 x₀≡1 或任意 xᵢ≡n-1 (i<t) 则通过(n 可能是素数);否则存在非平凡平方根 → n 合数。二次探测定理保证素数必通过;证明(OI Wiki / CP-Algorithms)指出对奇合数,能通过测试的基底至多占 φ(n)/4,故 k 轮误判 ≤4^(-k)。确定性界:GRH 下测 [2,2ln²n] 全部;实际工程对 <2^32 用 {2,7,61},<2^64 用 7 元组或前 12 个素数,均确定性正确(最小反例 3215031751=151·751·28351 骗过 {2,3,5,7})。

历史 · History

1976 年 Gary Miller 提出基于广义黎曼猜想的确定性素性测试;1980 年 Michael Rabin 将其改造为无条件概率算法(每轮 3/4 通过率)。此后经多轮基底优化形成实用的确定性变体(1980s 多位作者;7 元组基底来自 2015 年前后对 2^64 穷举验证)。CP-Algorithms 与 OI Wiki《素数》页均有标准实现与 2^64 确定性基底表。

How · 实现

伪代码

BASES_64 = [2,325,9375,28178,450775,9780504,1795265022]
def is_prime(n):
  if n < 2: return False
  for p in [2,3,5,7,11,13,17,19,23,29,31,37]:
    if n % p == 0: return n == p
  d, s = n-1, 0
  while d % 2 == 0: d //= 2; s += 1
  for a in BASES_64:
    a %= n
    if a == 0: continue
    x = pow(a, d, n)
    if x == 1 or x == n-1: continue
    for _ in range(s-1):
      x = x*x % n
      if x == n-1: break
    else: return False
  return True

Python 实现

import random


def is_prime_mr(n, bases=None):
    """Miller-Rabin 素性测试.
    - bases=None 时使用确定性基底: [2,325,9375,28178,450775,9780504,1795265022],
      对 < 2^64 的所有整数确定性正确.
    - bases 可传入自定义基底列表 (如随机基底做概率测试).
    小整数直接试除/特判."""
    if n < 2: return False
    small = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
    for p in small:
        if n % p == 0:
            return n == p
    d, s = n - 1, 0
    while d % 2 == 0:
        d //= 2
        s += 1
    if bases is None:
        bases = [2, 325, 9375, 28178, 450775, 9780504, 1795265022]
    for a in bases:
        a %= n
        if a == 0:
            continue
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        ok = False
        for _ in range(s - 1):
            x = x * x % n
            if x == n - 1:
                ok = True
                break
        if not ok:
            return False
    return True


def is_prime_probabilistic(n, k=12):
    """概率版: 随机 k 个基底. 误判概率 <= 4^{-k}."""
    if n < 2: return False
    if n in (2, 3): return True
    if n % 2 == 0: return False
    d, s = n - 1, 0
    while d % 2 == 0:
        d //= 2
        s += 1
    for _ in range(k):
        a = random.randint(2, n - 2)
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(s - 1):
            x = x * x % n
            if x == n - 1:
                break
        else:
            return False
    return True


if __name__ == "__main__":
    # 已知小素数/合数
    primes = [2, 3, 5, 7, 11, 13, 17, 19, 97, 9973, 1000003, 2147483647]
    comps = [1, 4, 6, 9, 341, 561, 1105, 1729, 41041, 825265, 3215031751]
    for p in primes:
        assert is_prime_mr(p), p
    for c in comps:
        assert not is_prime_mr(c), c

    # 强伪素数: 3215031751 = 151*751*28351, 是基底 {2,3,5,7} 的最小伪素数,
    # 但 7 基底确定性集 {2,325,...} 能正确判出合数.
    assert not is_prime_mr(3215031751), "3215031751 = 151*751*28351"
    # 展示 {2,3,5,7} 4 基底集确实被这个数骗过 (它是 32 位确定性界的最小反例)
    assert is_prime_mr(3215031751, bases=[2, 3, 5, 7]) == True

    # 2^64 附近的确定性验证
    mersenne_primes = [3, 7, 31, 127, 8191, 131071, 524287, 2147483647,
                       2305843009213693951, 618970019642690137449562111]
    for p in mersenne_primes:
        assert is_prime_mr(p), p

    # 随机交叉验证: 与试除法一致
    import random
    random.seed(2026)
    def trial(x):
        if x < 2: return False
        i = 2
        while i * i <= x:
            if x % i == 0: return False
            i += 1
        return True
    for _ in range(3000):
        n = random.randint(1, 10**7)
        assert is_prime_mr(n) == trial(n), n
    # 概率版
    for _ in range(500):
        n = random.randint(1, 10**6)
        assert is_prime_probabilistic(n) == trial(n), n
    print("miller-rabin: all tests passed")
PyPy 提示Python pow(a,d,n) 内建模幂是 C 级实现,务必使用而非手写快速幂。小质数预筛(前 12 个素数整除检查)能拦截 ~90% 的合数,大幅加速。大整数乘法在 PyPy 下为大整数运算,2^64 内仍轻松;上万位大数建议用 gmpy2 加速。确定性基底对 n 取模(a %= n)并跳过 a==0 的情况。
时间复杂度O(k log n) 次模乘(k 基底数);2^64 确定性仅需 7 轮
空间复杂度O(1)
注意 · 局限① 概率版理论上有误判概率(需多轮随机基底);② 确定性界仅对 2^64 内成立,更大整数需更多基底或改用 AKS/ECPP;③ 极端情况下 pow 的大整数乘法慢(超 2^128 建议 gmpy2);④ 错误基底选择(如只用 {2,3})会漏判 341、561 等强伪素数。

易错点

例题

OJ题号难度考点链接
LuoguP47184【模板】Pollard-Rho 算法:Miller-Rabin + Pollard-Rho 求最大质因子(前置素性判定)https://www.luogu.com.cn/problem/P4718
CP-AlgorithmsPrimality tests3标准实现 + 2^64 确定性基底表 + 32位 4 基底说明https://cp-algorithms.com/algebra/primality_tests.html
LuoguP10752质因数分解入门:验证素数与分解配合(小数据)https://www.luogu.com.cn/problem/P1075
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
上游 · 本知识点依赖(前置/变体/组合/共现)
下游 · 依赖本知识点

信源

✓ = 已核验 · △ = 未核验