素数判定算法

1、取余算法

对一个大于1的自然数 n 依次判断 2 → √n 能否整除 n,如果发现一个数能整除 n,那么 n 不是素数,否则是。

C++代码如下:

bool isPrime(int n) 
{
    if (n <= 1)
        return false;i
    for (int i = 2; i * i < n; i++)
        if (n % i == 0)
            return false;
    return true;
}

这种方法时间复杂度很高,我们可以借助数论知识进行进一步的优化。

素数分布规律:当 n >= 5 时,如果 n 为素数,那么 n % 6 = 1 || n % 6 = 5,即 n 一定出现在 6x(x ≥ 1)两侧。换句话说,任意一个素数都可以被表示为 6x ± 1 , x ϵ N 的形式。

证明:

把 6x 附近的数用以下方式表示: 
……(6x - 1), 6x, 6x+1, 2(3x+1), 3(2x+1), 2(3x +2), 6x + 5, 6(x+1)…… 
不在6x两侧的数为: 2(3x+1), 3(2x+1), 2(3x +2),它们不是素数,所以素数出现在 6x 的两侧。

因此可以得到以下优化:

bool isPrime(int n) 
{
    if (n <= 1)
        return false;
    if (n <= 3)    
        return true;
    if (n % 2 == 0 || n % 3 == 0)
        return false;
    for (int i = 5; i * i < n; i += 6)
        if (n % i == 0 || n % (i + 2) == 0)
            return false;
    return true;
}

简单取余算法复杂度过高,故只适合于判定较小的数。

2、Lucas-Lehmer算法

Lucas−Lehmer 算法只能用来判定梅森素数,即可以表示为 Mn = 2n − 1 的素数。

Lucas−Lehmer 序列定义如下:

\[s_i = \left \{   \begin{tabular}{c}   4 \hspace{1.4cm} when i = 0;  \\   s_{i-1}^2 - 2 \hspace{.5cm}   otherwise.   \end{tabular}  \]

其前五项的值为:

Term 0: 4,
Term 1: 4*4 – 2 = 14,
Term 2: 14*14 – 2 = 194,
Term 3: 194*194 – 2 = 37634,
Term 4: 37634*37634 – 2 = 1416317954, …

使用 Lucas−Lehmer 序列给定一整数 n 判定 x = 2n − 1 是否是梅森素数的步骤如下:

(1)根据公式 s[i] = (s[i-1]^{2}-2)\% x  计算出第 n - 1 项的值;

(2)如果该值为 0,则x是梅森素数,否则不为梅森素数。

代码如下:

bool isPrime(int p) 
{
 
    long long checkNumber = pow(2, p) - 1;
 
    long long nextval = 4 % checkNumber;
 
    for (int i = 1; i < p - 1; i++)
        nextval = (nextval * nextval - 2) % checkNumber;
 
    return nextval == 0;
}

3、AKS算法

AKS算法是由来自 Indian Institute of Technology Kanpur 的三名计算机科学家发明,取其姓氏首字母命名。其内容为:

一个数n是素数,当且仅当多项式 (x-1)^{n}-(x^{n}-1) 展开后的所有系数都可以被n整除。

代码如下:(参考自https://rosettacode.org/wiki/AKS_test_for_primes

long long c[100];
 
void coef(int n)
{
	int i, j;
 
	if (n < 0 || n > 63) abort(); // gracefully deal with range issue
 
	for (c[i=0] = 1; i < n; c[0] = -c[0], i++)
		for (c[1 + (j=i)] = 1; j > 0; j--)
			c[j] = c[j-1] - c[j];
}
 
int is_prime(int n)
{
	int i;
 
	coef(n);
	c[0] += 1, c[i=n] -= 1;
	while (i-- && !(c[i] % n));
 
	return i < 0;
}

AKS算法可以在多项式复杂度内判定任何数,但系数增长过快,依然不够实用。


目前并没有产生随机大素数的有效方法。因此要产生一个大素数的方法是,随机选取一个足够大的奇数并检验它是否是素数,如果不是则重新选取一个继续检验直到某个奇数通过测试。该方法的缺点是通过检验的奇数可能仍然不是素数,但可以按照需要做到使得通过检验的奇数不是素数的概率尽可能接近0。常用的概率判断方法有 Fermat 小定理、Miller−Rabin 算法及Solovay−Strassen算法。

4、Fermat小定理

如果 p 是素数,则对于任意的 a 满足 gcd(a,p) = 1,有a^{p-1}\equiv 1(mod\ p)a^{p-1}\cdot a\equiv a\cdot 1(mod\ p)

证明:

将 a 分别与 1→(p−1) 相乘,得到以下序列:

a,2a,3a,...,(p-1)a

如果对于n和m有:

na\equiv ma(mod\ p)

那么 n = mn = m,因为 p 不能被 a 整除

因此这 p − 1 项必然是各不相同的,于是将这些项除以 p 得到的余数 r_{1},r_{2},r_{3}...r_{p-1}必然是 1,2,3,...,(p−1) 的重排列,于是将每一项相乘有:

a\cdot 2a\cdot 3a\cdot ...(p-1)a\equiv 1\cdot 2\cdot 3\cdot ...(p-1)(mod\ p)

即 a^{p-1}(p-1)!\equiv (p-1)!(mod\ p),且 gcd( (p − 1)!, p) = 1,所以约去得

a^{p-1}\equiv 1(mod\ p)

 

值得注意的是,一个数是素数是其满足 Fermat 小定理的充分不必要条件,满足 Fermat 小定理的非素数称为 Fermat 伪素数,Fermat 伪素数的个数已被证明是无穷的,最小的Fermat伪素数是341=11×31。

使用 Fermat 小定理进行素数判定的步骤如下:

(1)重复以下步骤k轮:

    (a)随机选取[2,n-2]内的一个数;

    (b)如果该数满足模 n 的 Fermat 小定理,则返回 false;

(2)k轮后,返回true(可能是素数);

代码如下:

int quickPower(int a, int b, int p) 
{
    int ans = 1;
    while(b) 
    {
        if (b & 1)
            ans = ans * a % p;
        b >>= 1;
        a = a * a % p;
    }
    return ans;
}
 
bool isPrime(int n, int k) 
{
    if (n <= 1 || n == 4)
        return false;
    if (n <= 3)
        return true;
    while(k--) 
    {
        int a = 2 + rand() % (n - 4);
        if (quickPower(a, n - 1, n) != 1)
            return false;
    }
    return true;
}

5、Miller-Rabin算法

Miller-Rabin算法用于检测一个数n是否是素数。其时间复杂度上界为O(klog2(n)),其中k为检测的轮数。增大k可以提高Miller-Rabin算法的准确度。

可以通过拉宾米勒素数测试的合数为伪素数与Carmichael(强伪素数)

Carmichael数是非常少的,在1~100000000范围内的整数中,只有255个Carmichael数。

为此有二次探测定理 以确保该数为素数:

x^{2}\equiv 1(mod\ p),则 x\equiv 1(mod\ p)或 x\equiv p-1(mod\ p)

具体过程:(转自:https://blog.csdn.net/lbperfect123/article/details/85057358

// 18位素数:154590409516822759
// 19位素数:2305843009213693951 (梅森素数)
// 19位素数:4384957924686954497
LL prime[6] = {2, 3, 5, 233, 331};
LL qmul(LL x, LL y, LL mod) { // 乘法防止溢出, 如果p * p不爆LL的话可以直接乘; O(1)乘法或者转化成二进制加法
 
 
    return (x * y - (long long)(x / (long double)mod * y + 1e-3) *mod + mod) % mod;
    /*
	LL ret = 0;
	while(y) {
		if(y & 1)
			ret = (ret + x) % mod;
		x = x * 2 % mod;
		y >>= 1;
	}
	return ret;
	*/
}
LL qpow(LL a, LL n, LL mod) {
    LL ret = 1;
    while(n) {
        if(n & 1) ret = qmul(ret, a, mod);
        a = qmul(a, a, mod);
        n >>= 1;
    }
    return ret;
}
bool Miller_Rabin(LL p) {
    if(p < 2) return 0;
    if(p != 2 && p % 2 == 0) return 0;
    LL s = p - 1;
    while(! (s & 1)) s >>= 1;
    for(int i = 0; i < 5; ++i) {    
        if(p == prime[i]) return 1;
        LL t = s, m = qpow(prime[i], s, p);
        while(t != p - 1 && m != 1 && m != p - 1) {
            m = qmul(m, m, p);
            t <<= 1;
        }
        if(m != p - 1 && !(t & 1)) return 0;
    }
    return 1;
}

若 p 通过一次测试,则 p 不是素数的概率为25%;

若 p 通过 k 次测试,则 p 不是素数的概率为1/( 4 ^ k);

事实上,当 k = 5 时,p 不是素数的概率已为1/128,已经大于99.99%。通常认为,Miller-Rabin素性测试的正确率可以令人接受。

  • 12
    点赞
  • 32
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

Frost_Bite

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值