问题引入:在诸多问题中我们可能需要判断一个数是不是质数,但是如果给定的区间很大,那么如果我们用找因子的方式找素数会非常的慢,所以我们需要一个高效的筛素数的方法
方法一:暴力枚举因子
我们将1~n的数枚举一下它的因子,从2~sqrt(n),若能整除,则一定不是质数
代码
void ss(int n)
{
int vis[M];
for (int i=1;i<=n;i++) vis[i]=1;
for (int k=2;k<=sqrt(i);k++)
if (i%k==0) //暴力枚举它的每一个因子
{
vis[i]=0;break;
}
return ;
}
方法二:用素数去筛(Eratosthenes筛)
由所有的数都能分解成若干个质因子连乘积的形式可以得到,我们只要将所有素数的倍数抹掉,剩下的数一定是素数
代码
int ss(int n)
{
for (i=1;i<=n;i++) pri[i]=1;
pri[1]=0;//1和0都不是素数
for (i=1;i<=n;i++)
{
if (pri[i])//假如一个数是素数,那么不会被筛掉,所以如果pri=1,说明他是自己最小的因子,那么他可以去筛其他数
{
for (j=2;i*j<=n;j++)
{
pri[i*j]=0;//它的倍数都不是素数
}
}
}
}
该算法已经接近于线性筛,但还是不够快
方法三:方法二的优化版本,一般叫做欧拉线性筛
我们通过方法二发现,有的数同时是若干个质数的倍数,那么他会被筛若干次,显然不符合线性
那么我们就要想一个办法,使每个数都被筛一次,那不就是线性了吗?但是如何做到只筛一次呢?
我们令一个数能被筛掉,让他只被他最小的质因子筛掉,按照这个思想,我们只向现有的数上乘一个质因子,并且这个质因子是得到的合数的最小质因子,那么我们相当于是让合数的因子累积,举个例子,按方法二筛掉12,会有12=2*6,12=3*4,按照欧拉筛,我们会得到12=3*2*2
代码
int ss(int n)
{
int pri[M],cnt=0;
int vis[M];//vis存储当前数的最小质因子
for (int i=2;i<=n;i++)
{
if (!vis[i])
{
vis[i]=i;pri[++cnt]=i;
}//如果vis[i]=0,则说明他是质数,将他标记并压入质数数组中
for (int k=1;k<=cnt;k++)
{
if (pri[k]>vis[i]||pri[k]*i>n) break;//若当前的pri大于之前更新i的pri,则说明i有更小的质因子
v[i*pri[k]]=pri[k];//相当于是一个质因子连乘积的形式,并且当前的pri是他的最小质因子
}//用当前数去抹掉他的倍数
}
}