一、朴素判断素数算法
就判断素数而言,事实上是非常简单的了。根据定义,判断一个整数n是否是素数,只需要去判断在整数区间[2, n-1]之内,是否具有某个数m,使得n % m == 0。代码可以这么写:
int isPrime(int n) {
int i;
for (i = 2; i < n; ++i) {
if (n % i == 0) return 0;
}
return 1;
}
事实上,这个算法是O(n)的,感觉是很快了,但是依旧无法满足需求。所以有一个算法是O(sqrt(n))的算法。代码可以这么写:
int isPrime(long long n) {
long long i;
for (i = 2; i * i <= n; ++i) {
if (n % i == 0) return 0;
}
return 1;
}
原理很巧妙,也仅仅是把代码的i < n变成了i * i <= n而已,但是优化却是极其高的。可能你会注意到,在上一份代码里面,我定义的n为int类型,而后面一份代码,我却定义成了long long,这是因为在1s内,上一份代码能判断出来的数量级为1e8,而后面一份代码判断出来的数量级却几乎可以达到1e16。而原因仅仅是因为a * b = c的话一旦a是c的约数,那么b也是,如果a不是,那么b也不是。
这个方法也可以称作试除法。
二、Miller_Rabin素性测试
尽管上面的O(sqrt(n))的算法已经非常优秀了,但是面对更高数量级的“大数”却会显得力不从心。而这个时候,Miller_Rabin的优越性就显示出来了。
Miller_Rabin的理论基础来源于费马小定理。值得一提的是费马小定理是数论四大定理之一。
费马小定理:n是一个奇素数,a是任何整数(1≤ a≤n-1) ,则 a^(n-1)≡1(mod n)
要测试n是否是一个素数,首先将n-1分解为(2^s) * d,在每次测试开始时,先随机选择一个介于[1, N - 1]的整数a,之后如果对于所有的r∈[0, s - 1],若a^d mod N ≠ 1且a^((2^r) * d) mod N ≠ -1,那么n就是一个合数,否则n有3/4的几率是素数。
这也是为什么说这个算法只是素性测试了,他不能完全保证结果正确,但是当测试次数大约为20的时候,基本可以确保正确率达到97%以上。
它的代码可以这么写:
const int S = 20;
LL mod_mul(LL a, LL b, LL n) {
LL res = 0;
while (b) {
if (b & 1) res = (res + a) % n;
a = (a + a) % n;
b >>= 1;
}
return res;
}
LL mod_exp(LL a, LL b, LL n) {
LL res = 1;
while (b) {
if (b & 1) res = mod_mul(res, a, n);
a = mod_mul(a,