素数判定及筛的总结

本文只写了较常见的几种素数求法及筛法,实际上方法多种多样,但这些也已经足够用了(学不会其他的)

概念

素数研究是数论中最古老、也是最基本的部分,素数即质数
定义为在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数

性质

1.素数p的约数:1p

2.唯一分解定理:任意大于 1 的自然数,要么本身是素数,要么可以分解为几个素数之积,且这种分解是唯一的

3.素数的个数是无限的

常用的判断素数的方法:

1.试除法

在特判 0 1 2 后,再在区间 [2,n) 之间试图找出一个数能把 n 整除,若找到,则返回 0 ,说明 n 不是素数,若没找到,最终返回 1 ,说明 n 是素数

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

改进:
对以上代码,如果我们输入 1e9+7 来看看运算时间

xf.png

这太慢了!在算法竞赛里绝大部分的题都要求在 1s 内出结果
开始优化
我们知道所有自然数的因数都是成对出现的,并且分布在区间 [1,√n][√n,n] 内,如果因数为 √n 则认为有两个因数 √n ,不过是重复的而已
于是可以对刚才的for做点改动
我们没必要一直试除到 i=n-1 ,因为如果在 [2,√n] 内都找不到能把 n 整除的数,那么 [√n,n) 内也一定找不到能把 n 整除的数
于是乎:

int f(int n){
	if(n<=1)return 0;
	if(n==2)return 1;
	int x=sqrt(n);
	for(int i=2;i<=x;i++)
		if(n%i==0)
			return 0;
	return 1;
}

同样输入 1e9+7 试试
xf2.png

ohhh差距异常明显

那再试试 1e18+7 呢(已将数据类型改为long long)
xf2.png

2.取模法

利用数学知识

n≥6 时, n 可以表示为 6x+1、6x+2、6x+3、6x+4、6x+5、6x (x≥1) 中的一种

那么,若 n 的表达式为 6x+2、6x+4或6x ,很显然 n 不是素数

故当 n 的表达式为 6x+1或6x+5 时,可能为素数

再应用试除法中改进的方法:

int f(int n) {
	if (n == 1)return 0;
	if (n == 2 || n == 3)return 1;
	if (n % 6 != 5 && n % 6 != 1)return 0;
    int x=sqrt(n);
	for (int i = 5; i < x; i += 6) {
		if (n % i == 0 || n % (i + 2) == 0)
			return 0;
	}
	return 1;
}

我们来直接试试1e18+7
1122.png

3.埃氏筛

先引入一个问题:

在区间 [ 0,10^6 ] 内进行 10^6 次询问,每次询问输入一个在此区间内的 n ,判断 n 是否为素数

对于该问题,即使我们已经把素数判断写成了函数以多次调用,但调用 10^6 次是否太低效了,我们能否通过一种预处理的方法把 10^6 内的所有素数筛选出来

首先公认的:一个素数的倍数一定不是素数

于是对于一连串自然数: 2 3 4 5 6 7 8 9 10 11 12 13 14 15…

先把2的倍数划去: 2 3 5 7 9 11 13 15…

再把3的倍数划去: 2 3 5 7 11 13…

以此类推,最后留下来的数就是素数

首先准备好一个判断是否素数的数组 vis[N] ,下标为要判断的数,值为 1 代表被筛掉的非素数(数组开在全局时所有值默认为 0 )
上代码

#include<iostream>
using namespace std;
const int N=1e6+7;
int vis[N];
void getprime(){
	vis[0]=vis[1]=1;//0 1不是素数
	for(int i=2;i<N;i++)
		if(!vis[i])//如果i是素数,开筛!
			for(int j=i*2;j<N;j+=i)
				vis[j]=1;
}
int main(){
	getprime();
	//打印1000以内的素数
	for(int i=0;i<=1000;i++)
		if(!vis[i])
			cout<<i<<" ";
      return 0;
} 

这个时间复杂度我算不来。。直接贴 O(nloglogn)
实际上 getprime 还可以优化
开筛时的for先筛了 2 的所有倍数,然后下一次for再去筛 3 的所有倍数
然而筛除 3 的倍数时,我们又从 32 倍开始筛,其实 3 * 2 ,已经被 2 * 3 时筛过了
再筛 5 的倍数时,从 52 倍开始筛,但是 5 * 2 会先被 2 * 5 筛去, 5 * 3 会先被 3 * 5 会筛去,所以我们每一次只需要从 i * i 开始筛,因为 n2,3,4…n-1 倍已经被筛过了
另外,判断n是不是质数,我们只判断 [2,√n] 内有没有它的因数
在素数筛中,一样可以这样做,因为一个数的最小质因数一定小于等于√n

所以对于区间 [0,N] ,最大的数是N, 它的最小质因数就一定小于等于√N

所以只需要用 [0,√N] 中的质数就可以筛去 [0,N] 中所有的非素数
于是得到

void getprime() {
	vis[0] = vis[1] = 1;//0 1不是素数
	int x = sqrt(N);
	for (int i = 2; i <= x; i++)
		if (!vis[i])//如果i是素数,开筛!
			for (int j = i * i; j < N; j += i)
				vis[j] = 1;
}

这样时间复杂度接近 O(n)

接下来测试一下筛 [0,10^6 ] 过程所用时间:
8.png

如果再增加一个问题,输出从 2 开始的 100 个素数

很简单,只要再开个数组 prime[N] 来存放素数就行了,在 getprime 里进行改动:

void getprime() {
	vis[0] = vis[1] = 1;//0 1不是素数
	int x = sqrt(N),cnt=0;
	for (int i = 2; i <= x; i++)
		if (!vis[i]){
            prime[cnt++]=i;
			for (int j = i * i; j < N; j += i)
				vis[j] = 1;
        }
}

然后对 prime 数组进行输出:
T8K18I2.png

埃氏筛的缺点,在达到 10^7 以上时时间好像消耗有点多

4.欧拉筛

在埃氏筛中,核心是去筛掉每一个质数的倍数,显然后面会有数被他的多个质因数筛去,过多消耗时间,例如 210 会被 2 3 5 7 各筛一次,于是对针对这种多次筛去同一个数的情况进行优化就得到了欧拉筛,他能把时间复杂度降到真正的 O(n) ,所以又称线性筛

欧拉筛的核心是 保证每个非素数都只会被他的最小质因数筛掉

void getprime() {
	int cnt = 0;
    vis[0]=vis[1]=1;
	for (int i = 2; i < N; i++) {
		if (!vis[i]) prime[cnt++] = i;
        //遍历已知素数表
		for (int j = 0; j < cnt; j++) {
            int x=prime[j]*i;
			if (x <= N)
				vis[x] = 1;//筛掉
			else
				break;
            //当在已知质数表中找到了一个质数能整除i
            //说明找到的这个质数prime[j]就是i的最小质因数
			if (i % prime[j] == 0) break;
        }
	}
}

待更新

5.Miller - Rabin 素数检测算法

不更了,M-R太难了

本文程序计时模板

#include <iostream>
#include <time.h>
using namespace std;
int main() {
	//clock_t为CPU时钟计时单元数
	clock_t start, finish;
	//clock()函数返回此时CPU时钟计时单元数
	start = clock();

	//待测时的代码执行区:
	//......

	//clock()函数返回此时CPU时钟计时单元数
	finish = clock();
	//finish与start的差值即为程序运行花费的CPU时钟单元数量,再除每秒CPU有多少个时钟单元,即为程序耗时
	cout << "time:  " << double(finish - start) / CLOCKS_PER_SEC << "s" << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花崽oyf

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值