素数筛
素数筛的基本思想:
-
标记一个范围内的数字是否为合数,没有标记的为素数
-
算法的空间复杂度为O(N),时间复杂度为O(N * loglogN)
-
总体的思想是用素数去标记掉不是素数的数字,例如我知道了 i 是素数,那么2 * i、3 * i、4 * i…就都不是素数
一、原始筛法
暴力枚举,代码简单,但时间复杂度高,运算较大的数字有明显卡顿感。
思路: 列举2 ~ sqrt(n)的所有数字,用n去除以,若都不能被整除,n就是质数
int is_prime1(int n){
for(int i=2,I=sqrt(n);i<I;i++){
if(n%i==0)return 0;
}
return 1;
}
二、埃氏筛法
思路: 0表示是素数,1表示不是素数。0,1均不是素数,从2开始,把2的倍数标记为1,一直到大于n,然后从下一个素数3开始,进行同样的操作。
#define MAX_N 100
int not_prime[MAX_N] = {1,1,0};
int prime[MAX_N] = {0};
void Erat_prime(int n){
for(int i=2;i<=n;n++){
if(!not_prime[i])prime[++prime[0]]=i;
for(int j=2;j*i<=n;j++){
not_prime[i*j]=1;
}
}
}
//对于内层循环代码,先标记首个数为素数,再将它的倍数标记为合数
for(int j=2*i;j<=MAX_N;j+=i){
not_prime[j]=1;
}
因为这种标记重复太多,比如2把6标记一次,3又把6标记一次。对此,我们可以优化一下,进行向上标记,优先标记自己的倍数,如下:
for(int j = i*i;j<=MAX_N;j+=i){
not_prime[j]=1;
}
但是这样标记会出现一个问题,比如 i 循环到 MAX_N ,j = MAX_N * MAX_N,这样就会出现爆栈的情况,于是,我们再做以下优化
for(int j=j;j*i<=n;j++){
not_prime[i*j]=1;
}
三、欧拉筛
思路: 在用埃氏筛的时候,同一个数字也许会被筛选多次,欧拉筛就是在埃氏筛的基础上,让每个合数只被它的最小质因子筛一次,以达到不重复的目的
用一条语句if(i%prime[j]==0)break;
,使每个合数只被它的最小质因子筛选一次,从而避免重复筛选
#define MAX_N 100
int prime[MAX_N+5] = {0};
int is_prime[MAX_N+5] = {0};
void init_prime(){
for(int i=2;i<=MAX_N;i++){
if(!is_prime[i])prime[++prime[0]]=i;
for(int j=0;j<prime[0];j++){
if(prime[j]*i>MAX_N)break;
is_prime[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}