素性测试算法
在我的 github 博客上有一篇类似的文章,欢迎访问。
素数拥有许多特殊的性质,这让它在计算机科学中被广泛应用。
例如,著名的 RSA 加密算法基于两个质数的乘积的难分解性;同时,其正确性基于质数的费马小定理和一些推论。
因此,快速判定一个大整数是否是质数是重要的课题。
试除法
由于质数 \(p\) 只有 \(1\) 和 \(p\) 两个正因数,对于正整数 \(n\),我们可以暴力检验 \([2,n-1]\) 内的数是否是。
不过,由于一个大于 \(\sqrt{n}\) 的因数必定对应一个小于 \(\sqrt{n}\) 的因数,我们可以将算法优化到检验 \([2,\sqrt{n}]\) 间的整数。
这样,考虑到两个 \(O(n)\) 位的大整数作除法最快大约是 \(O(n\log^2n\log\log n)\),试除法的时间复杂度为 \(O(\sqrt{n}\log n\log^2\log n\log\log\log n)\)。
对于小整数(小于 \(2^{64}\)),我们可以忽略除法的时间复杂度,\(O(\sqrt{n})\) 勉强可以接受。
但对于 RSA 算法要求的大整数,这样的速度实在太慢。
素性测试
素性测试是在不分解给定数字的前提下,判断数字是否是质数的算法。
通常将素性测试分为两种:
- 确定性测试:如 AKS 算法、椭圆曲线素性证明。
- 概率性测试:如 Miller-Rabin 素性测试、Baillie–PSW 素性测试、Solovay–Strassen 素性测试等。这类算法返回
false
时,表明输入是合数;反之,说明可能是质数。
Fermat 测试
最早用于测试素数的可能是 Fermat 小定理 i.e. 若 \(p\) 是质数:
因此,我们可以在 \([2,n-1]\) 随机取值(记为 \(a\)),验证 \(a^{n-1}\equiv 1\pmod n\) 是否成立。
然而,Fermat 小定理的逆定理实际并不成立,例如对于 \(n=341=31\times 11,a=2\),有 \(a^{340}\equiv 1\pmod {341}\)。
更可怕的是,存在一类数 \(n\),对于任意 \(a\perp n\),满足 \(a^{n-1}\equiv 1\pmod n\)。这种数被称作 Carmichael 数。Carmichael 数有无穷多个。
Miller-Rabin 素性测试
这是 Fermat 测试的改进,基于另一个关于质数的定理:对于质数 \(p\),在模 \(p\) 意义下,\(1\) 的平方根有且只有 \(\pm 1\)。
因此,我们将指数 \(n-1\) 写成 \(2^sd\) 的形式,其中 \(s\) 为正整数而 \(d\) 为奇数。
那么,若下列条件有一个成立,称 \(n\) 的素性被 \(a\) 验证:
- \(a^d\equiv 1\pmod n\)
- \(a^{2^rd}\equiv -1\pmod n\),对于某个 \(0\le r<s\)
值得庆幸的是,不存在一个合数 \(n\) 能通过所有基数的验证。更进一步地,假设 GRH(广义黎曼猜想)成立,只需检查 \([2,\min(n-2,\lfloor2\ln^2n\rfloor)]\) 内地所有整数即可确定 \(n\) 的素性。
实际上,Miller-Rabin 的确定性版本最早在 1976 年由 Gary L. Miller 提出,但由于其正确性依赖于未经证明的 GRH,尽管后者被相信是成立的,前者仍不被承认为一个确定的素性判断算法。
1980 年,Michael O. Rabin 将其修改为无依赖条件的概率算法。
使用 FFT 优化大整数乘法时,\(k\) 轮 Miller-Rabin 的时间复杂度为 \(O(k\log^2n\log\log n\log\log\log n)\),此时判定错误的概率为 \(4^{-k}\)。我们可以测试足够多轮直到错误的概率低于 \(2^{-100}\),这意味着更可能出现计算机的硬件错误而非算法判定错误。
ASK 算法
无论如何是要提一句的。
在 2002 年 8 月 6 日,Manindra Agrawal、Neeraj Kayal 和 Nitin Saxena 发表的论文《PRIMES is in P》注定被载入史册。
这是首个在多项式时间内确定给定数字是否为质数的算法,不依赖于任何未证明的猜想。至此,我们知道了判定质数问题属于 P 问题。
AKS 是第一个同时具有一般性、多项式时间、确定性和无条件正确的原始性证明算法。以前的算法已经开发了几个世纪,最多实现了其中的三个属性,但不是全部四个。
- AKS 算法可用于验证给定的任何一般数的素数。已知许多快速素数检验仅适用于具有某些性质的数字。例如,Lucas-Lehmer 检验仅适用于梅森数,而 Pépin 检验仅适用于费马数。
- 算法的最大运行时间可以由目标数字中的位数上的多项式来限制。ECPP 和 APR 最终证明或反驳给定数是质数,但不知道所有输入都有多项式时间界限。
- 该算法保证可以确定性地区分目标数是素数还是合数。随机检验,如 Miller-Rabin 和 Baillie-PSW,可以在多项式时间内测试任何给定数字的素数,但已知只能产生概率结果。
- AKS 的正确性不以任何未经证实的辅助假设为条件。相比之下,Miller 版本的 Miller-Rabin 检验是完全确定性的,并且在所有输入的多项式时间内运行,但其正确性取决于尚未证明的广义黎曼假设的真实性。
虽然该算法在理论上具有巨大的重要性,但它并未在实践中使用,使其成为一种银河式算法。对于 64 位输入,Baillie-PSW 测试是确定性的,运行速度要快许多数量级。对于较大的输入,ECPP 和 APR 测试的性能(也是无条件正确的)远远优于 AKS。此外,ECPP 可以输出一个原始证书,该证书允许对结果进行独立和快速的验证,这是 AKS 算法无法实现的。
——译自英文维基
实现
在 OI 领域,Miller-Rabin 用于快速判断质数。
这是一个随机选取基数的判断函数,test_time
指定测试的轮数。
bool millerRabin(int n) {
if (n < 3 || n % 2 == 0) return n == 2;
if (n % 3) return n == 3;
int u = n - 1, t = 0;
while (u % 2 == 0) u /= 2, ++t;
for (int i = 0; i < test_time; ++i) {
int a = rand() % (n - 3) + 2, v = quickPow(a, u, n);
if (v == 1) continue;
int s;
for (s = 0; s < t; ++s) {
if (v == n - 1) break;
v = (long long)v * v % n;
}
if (s == t) return false;
}
return true;
}
尽管这几乎不可能出错,但基于随机的算法总是让人放不下心,那么:
- 对于 \([1,2^{32})\) 内的整数,只需取 \(\{2,7,61\}\) 判断即可。
- 对于 \([1,2^{64})\) 内的整数,只需取 \(\{2, 325, 9375, 28178, 450775, 9780504, 1795265022\}\) 判断即可,为了方便记忆,也可以取前 \(12\) 个质数。
注意以上列举的基数必须全部取一遍,而不是只取小于 \(n\) 的数。不过,若某轮取的基数 \(b\) 满足 \(n\mid b\),可以认为通过本轮测试。