质数筛

在算法题中质数判断是经常出现的问题,下面介绍几种算法:

1、朴素判断(改进)

首先是最容易理解,也是时间复杂度最高的算法 -> 单次判断O(√n)

先放代码

 #include <cmath>
 
 int PrimeChoose(int n)
 {
     int i;
     int is_prime = 1;
     if (n == 1)
         return 0;
     for(i=2;i<=sqrt(n);i++)
         if (n % i == 0)
         {
             is_prime = 0;
             break;
         }
     return is_prime;
 }

众所周知,1不是质数,

而合数必然能由一个 >= sqrt(n) 与一个 <= sqrt(n)的数字相乘得到,

所以我们只需要从2到sqrt(n)挨个判断n能否被其整除就行了。

当进行少量数判断时可以使用这种方法,

在测试量很大时,这种方法显然是不可取的。

当我们需要判断1~n范围内数字是否为质数可以用以下两种方法:当n很大时效率便非常高

2、埃氏筛(埃拉托斯特尼筛法)

时间复杂度O(n ln ln n)

这种算法还是比较容易理解的

依据:

1).若X是素数,则X的倍数都不是素数
2).若X不是素数,则X肯定在[1,X)之间被筛选掉。即存在Y,使得k*Y=X,然后Y根据推理1) 判定X为非素数。
 由此我们可以得到 :若X未被事先筛选掉则X为素数。

那么下面我们就可以写代码了!!

bool prime[MAXN] = {};

void Prime(int n)
{
    for (int i = 2; i <= n; i++)
    {
        prime[i] = true;
    }
    for (int i = 1; i * i <= n; i++)
    {
        if (prime[i])
        {
            for (int j = i * i; j <= n; j += i) //j从i²开始判断更高效,因为之前2*i,3*i等等已经被筛除了
            {
                prime[j] = false;
            }
        }
    }
 } 

我们可以发现用此筛法筛选时,一个数可能被多次筛选,例如:12被2筛除又被4筛除。此算法显然有改进空间。

3、欧式筛(线性筛)

这个算法的时间复杂度达到了惊人的O(n),也是我已知最高效的筛法;

欧式筛保证了每一个合数都只被其最小的质因子筛除;

先上代码

int prime[MAXN];
bool vis[MAXN] = {};
int cnt=0;
void Euler_prime(int n)
{
   vis[1] = ture;
    for(int i=2;i<=n;++i)
    {
        if(!vis[i])
        {
       prime[cnt++]=i;
     }
        for(int j=0;j<cnt;++j)
        {
            if(i*prime[j]>n)//判断是否越界
                break;
            vis[i*prime[j]]=true;//筛数
            if(i%prime[j]==0)
                break;
        }
    }
}

筛除的原理是:

用当前的 i 去寻找 <= i 的质数 两者相乘所筛除的数其最小质因子一定是该质数。

下面的代码保证了该原理的正确性,也是O(n)的关键。

这段代码是核心中的核心,真的NB!!!

 if(i%prime[j]==0)
                break;

当i是prime[j]的整数倍时,记 k = i / prime[j],那么 i * prime[j+1] 就可以变为 (k * prime[j+1]) * prime[j],

这说明 i * prime[j+1] 是 prime[j] 的整数倍,不需要现在筛出,因为在之后筛除过程中i * prime[j+1] 这个合数一定会被prime[j]筛除,

prime[j]之后的所有素数同理,所以break跳出循环。

4、区间筛

如果要筛[a,b)中的质数,

因为b以内的合数的最小质因数一定不超过sqrt(b),

如果有sqrt(b)以内的素数表的话,就可以把埃式筛法运用在[a,b)上了。

也就是说,先分别做好[2,sqrt(b))的表和[a,b)的表,然后从[2,sqrt(b))的表中筛得素数的同时,也将其倍数从[a,b)的表中划去,

最后剩下的就是区间[a,b)内的素数了。

在制作[a,b)的表我们用到下标偏移,用来减小内存开销防止MLE。

放上代码

bool is_prime_small[MAXN1]; //区间[2,sqrt(b))
bool is_prime[MAXN2]; //区间[a,b)

//对区间[a,b)内的整数执行筛法,is_prime[i-a]=true <=> 表示i是素数(下标偏移了a) 
void segment_sieve(long long a, long long b)
{

    long long i, j;
    for ( i = 0; i * i < b; i++) //对[2,sqrt(b))的初始化全为质数
        is_prime_small[i] = true;
    for ( i = 0; i < b - a; i++) //对下标偏移后的[a,b)进行初始化
        is_prime[i] = true;

    for ( i = 2; i * i < b; i++) 
    {   
        //筛选[2,sqrt(b))
        if (is_prime_small[i]) 
        {
            for ( j = 2 * i; j * j < b; j += i)
                is_prime_small[j] = false;
            //(a+i-1)/i 得到最接近a的i的倍数,最低是i的2倍,然后筛选
            for (j = (2LL>(a + i - 1)/i? 2LL: (a + i - 1) / i) * i; j < b; j += i)
                is_prime[j - a] = false;
        }
    }
}

当a == 1时,a不会被筛除 >>>>> 这种情况也用不到这个算法。。。。。

posted @ 2021-03-11 22:40  ReyHan  阅读(323)  评论(0)    收藏  举报