浅谈质数的判定

质数的定义

质数又称素数,是指在大于 1 1 1 的自然数中,除了 1 1 1 和它本身以外不再有其他因数的自然数。

质数的判定

暴力筛选法

假设我们要判定 n n n 是否为质数, n n n 暴力做法是从 2 2 2 枚举到 n − 1 n-1 n1,看其中是否存在一个自然数可以整除 n n n。有一个要注意的点,要特判 n = 2 n=2 n=2 的情况,直接就返回是即可。时间复杂度是: O ( n ) \mathcal{O}(n) O(n)
代码

bool isp(int n){
	if(n<=2){
		return n>=2;
	}
	for(int i=2;i<n;i++){
		if(n%i==0){
			return 0;
		}
	}
	return 1;
}

暴力筛选法优化

这样做是十分稳妥了,但是真的有必要每个数都去判断吗?
很容易发现这样一个事实:如果 x x x a a a 的约数,那么 a x \frac{a}{x} xa 也是 a a a 的约数。
这个结论告诉我们,对于每一对 ( x , a x ) (x,\frac{a}{x}) (x,xa),只需要检验其中的一个就好了。为了方便起见,我们之考察每一对里面小的那个数。不难发现,所有这些较小数就是 [ 1 , a ] [1,\sqrt{a}] [1,a ] 这个区间里的数。时间复杂度为: O ( n ) \mathcal{O}(\sqrt{n}) O(n )
代码

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

Fermat 素性测试

Fermat 素性检验是最简单的概率性素性检验
我们可以根据费马小定理得出一种检验素数的思路:
基本思想是不断地选取在 [ 2 , n − 1 ] [2,n-1] [2,n1] 中的基 a a a,并检验是否每次都有 a n − 1 ≡ 1 ( m o d n ) a^{n-1} \equiv 1 \pmod n an11(modn)
代码

#include<bits/stdc++.h>
using namespace std;
int qp(int a,int b,int c){
	int n=1;
	while(b>0){
		if(b&1){
			n=(n*a)%c;
			b--;
		}
		a=(a*a)%c;
		b>>=1;
	}
	return n%c;
}
bool isp(int n){
    if(n<=2) return n==2;
    // test为测试次数,建议设为不小于 8
    // 的整数以保证正确率,但也不宜过大,否则会影响效率
    int test=10;
    for(int i=1;i<=test;i++) {
      int a=rand()%(n-2)+2;
      if(qp(a,n-1,n)!=1) return 0;
    }
    return 1;
}

Miller-Rabin 质数检测

根据上面的 Fermat 素性测试,我们可以加上一个二次探测定理即可实现更准确的判断。如果 p p p 是奇素数, x 2 ≡ 1 ( m o d p ) x^2 \equiv 1 \pmod p x21(modp) 的解为 x ≡ 1 ( m o d p ) x \equiv 1 \pmod p x1(modp) 或者 x ≡ p − 1 ( m o d p ) x \equiv p - 1 \pmod p xp1(modp)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int qp(int a,int b,int c){
	int n=1;
	while(b>0){
		if(b&1){
			n=(n*a)%c;
			b--;
		}
		a=(a*a)%c;
		b>>=1;
	}
	return n%c;
}
bool millerRabin(int n){
	if(n<3||n%2==0) return n==2;
	if(n%3==0) return n==3;
	int u=n-1,t=0;
	while(u%2==0) u/=2,t++;
	//test_time 为测试次数,建议设为不小于 8
	//的整数以保证正确率,但也不宜过大,否则会影响效率
	int test_time=10;
	for(int i=0;i<test_time;i++){
		//0,1,n-1可以直接通过测试, a 取值范围 [2, n-2]
		int a=rand()%(n-3)+2,v=qp(a,u,n);
		if(v==1) continue;
		int s;
		for(s=0;s<t;s++){
			if (v==n-1) break;//得到平凡平方根 n-1,通过此轮测试
			v=v*v%n;
		}
		//如果找到了非平凡平方根,则会由于无法提前 break; 而运行到 s == t
		//如果 Fermat 素性测试无法通过,则一直运行到 s == t 前 v 都不会等于 -1
		if(s==t) return 0;
	}
	return 1;
}
main(){

	return 0;
}

埃氏筛

如果我们要筛选出从 1 1 1 n n n 的质数怎么办呢?我们就要用到埃氏筛了。先从 2 2 2 开始枚举质数,把从 1 1 1 n n n 范围内能被当前数整除的自然数,都标记为合数,然后找到下一个质数重复操作。时间复杂度为: O ( n log ⁡ log ⁡ n ) \mathcal{O}(n\log{\log{n}}) O(nloglogn)
代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int k,p[500005];
bool isp[500005];
void work(int n){
    isp[0]=isp[1]=0;
    for(int i=2;i<=n;i++){
    	isp[i]=1;
    } 
    for(int i=2;i<=n;i++) {
        if(isp[i]==1){
            p[++k]=i;
            if(i*i>n) continue;
        	for(int j=i*i;j<=n;j+=i)
            // 因为从 2 到 i - 1 的倍数我们之前筛过了,这里直接从 i 的倍数开始,提高了运行速度
            isp[j]=0;  // 是 i 的倍数的均不是素数
        }
    }
}
main(){
	
	return 0;
}

线性筛

埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。如果能让每个合数都只被标记一次,那么时间复杂度可以降到 O ( n ) \mathcal{O}(n) O(n)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int k,p[100005];
bool vis[100005];
void work(int n){
    for(int i=2;i<=n;i++){
        if(vis[i]==0) p[++k]=i;
        for(int j=1;j<=k&&p[j]*i<=n;j++){
            vis[p[j]*i]=1;
            if(i%p[j]==0){
                // 换言之,i 之前被 pri_j 筛过了
                // 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定会被
                // pri_j 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break 掉就好了
            	break;
			} 
        }
    }
}
main(){
	
	return 0;
}

参考资料

https://oi-wiki.org/math/number-theory/sieve/

https://oi-wiki.org/math/number-theory/prime/

https://baike.baidu.com/item/质数/263515#3

https://blog.youkuaiyun.com/qq_43695957/article/details/116062333

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值