素数,是指因子只包含1和其本身的数,那么,我们怎么判断素数呢?

(以下代码均基于打表(1~1e6)的基础上完成)

1.按照定义计算

素数的定义就是一个数的因子只包含1和其本身,那么我们直接就按照定义写:

#include<stdio.h>
#include<string.h>
#define maxn 1000000+10
int pri[maxn];
int isprime(int n)
{
	for(int i=2;i<n;i++)
		if(n%i==0)
			return 0;
	return 1;
}
int main()
{
	pri[1]=0;
	for(int i=2;i<=maxn;i++){
		pri[i]=isprime(i);
	}
	return 0;
}

这是最基础的写法,也是最小白的写法。毕竟,当时我刚大一加入大学的某个协会时,考的就有判断素数...

这种算法的复杂度为O(n^2),复杂度非常的大,对于1e6的数据范围来说肯定要超时,那么还有没有更优化的算法?答案是肯定的

 

2.基于定义计算的优化算法

我们对一个合数进行考虑,例如12:

它的因子有1 2 3 4 6 12 ,而且1*12=12 ,  2*6=12 , 3*4=12

可见,每一个因子都会有另一个对应的因子,观察可得,它们的分布是平均的,左边的一半对应右边的一半,那么最中间的分界线应该是什么? √n

因此,我们只需要对√n 前面的数字进行判断即可。

#include<stdio.h>
#include<string.h>
#define maxn 1000000+10
int pri[maxn];
int isprime(int n)
{
	for(int i=2;i*i<=n;i++)//只需要将i变成i*i即可 
		if(n%i==0)
			return 0;
	return 1;
}
int main()
{
	pri[1]=0;
	for(int i=2;i<maxn;i++){
		pri[i]=isprime(i);
	}
	return 0;
}

这种算法的复杂度要比上一种好的多,复杂度为O(n√n),但是对于1e6的数据范围来说还是太大了。有没有再快一点的算法?

3.素数筛选法

素数筛选法的思想为:

从2开始,因为2的倍数一定不是素数,所以先把2的倍数全部删去;

接着找下一个素数3,把3的倍数全部删去;

因为4是2的倍数,已经被删去,所以直接找下一个素数5,把5的倍数全部删去;

接着7的倍数,11的倍数,......直到把1e6范围内的合数全部筛选出去,剩下的即为素数:

//以下优化均基于打表的基础上
 
#include<stdio.h>
#include<string.h>
#define maxn 1000000+10
int vis[maxn];
void isprime()
{
	memset(vis,0,sizeof(vis));//此处vis[i]=1表示不是素数,vis[i]=0表示是素数 
	vis[1]=1;
	//由于i*i的数据范围可能会超过int,所以需要用long long表示
	for(long long i=2;i*i<=maxn;i++){//此处有优化,因为如果一个合数>sqrt(maxn),那么他必定在前面已经被标记过。 
		if(!vis[i]){
			for(long long j=i*i;j<=maxn;j+=i){//此处也有优化,我们只需要判断从i*i开始判断即可。 
				vis[j]=1;
			}
		}
	}
}
int main()
{
	int n;
	isprime();
	return 0;
}

这种算法的复杂度应该为O(n);,是一种非常快速的判断素数的算法。

上述代码有两处优化,第一处优化的证明如下:

假设 maxn > i > sqrt(maxn)并且为合数,那么,他肯定会有一个因子小于等于sqrt(maxn),因此,i一定在之前已经被标记过了。

第二处优化证明为:

假设i>2,那么对于 i * ( i - 1 ):

如果 i - 1是素数,那么 i * ( i - 1 ) 一定在之前已经被标记过;

否则,如果 i - 1 是合数,那么  i - 1能被分成更小的素数。设其中一个为a,那么 i * ( i - 1 )= i * ( i - 1 ) / a * a 也一定被标记过。

优化后的算法时间会节省非常多,在平常的算法竞赛中,用上述代码就已经可以解决大部分的涉及素数打表的问题。