从最基础的讲起
素数,也称质数,指大于1,且不被除1与它本身以外的其它数整除的数。
朴素的求素数表的方法是直接枚举每个数,判断它是否是素数。
而判断是否为素数的方法,最朴素的是枚举
到
判断是否整除,若都无法整除则为素数。
for (int i=2;i<=n;i++)
{
bool flag=false;
for (int j=2;j<i;j++)
if (i%j==0)
{
flag=true;
break;
}
if (!flag) prime[++cnt]=i; //加入素数表
}
从代码可以看出,如果要求以内的所有素数,则时间复杂度为
。
改进判断素数的方法
容易发现,若对
取模的结果为0,那么因为
,则
对
取模的结果也等于0;这即是说,判断前
个数与判断后
个数是等价的(
)。那么,我们只用枚举
到
即可,时间复杂度降为
.。
代码是差不多的,只是枚举的上限变了:
for (int i=2;i<=n;i++)
{
bool flag=false;
for (int j=2;j<=int(sqrt(i));j++)
if (i%j==0)
{
flag=true;
break;
}
if (!flag) prime[++cnt]=i;
}
其它判断素数的方法
费马小定理:如果为素数且
模
不等于0,那么
。
(表示同余,可以理解为
对
取模的值为1)
利用这一性质,我们可以随机选取几个,利用快速幂判断余数是否都为1,若否,则不为素数。算法复杂度为
。
另外也有一种方法更为简单。首先,如果一个数是偶数,那它一定不为素数;而如果数为奇合数,那么至少存在
个能证明它为合数的数,亦即是说,每选一个数出来约有
的概率检验成功,那么选k个数检验失败的概率为
,出错概率极小。时间复杂度为
。
当然,上述两种都是随机化算法,有一定概率出错,一般是不需要用的。
改进筛选素数的方法
素数筛法有两种。
根据素数的唯一分解定理:任意一个数都能分解为
,其中
为质因数。我们可以在筛选素数的时候标记合数。
(唯一分解定律也用来分解质因数)
用的布尔型数组标记合数,在枚举到某个数时,除了用
判断其是否为素数,还要与素数表里的其它素数相乘标记后面的合数。
可以证明:若枚举到为止,
仍未被标记,那么
为素数。(因为不可能从更大的数标记到更小的数)
for (int i=2;i<=n;i++)
{
if (!flag[i]) prime[++cnt]=i;
for (int j=1;j<=cnt;j++)
if (i*prime[j]<=n)
flag[i*prime[j]]=true;
}
另一种是时间复杂度为的欧拉线性筛。为了保证高效率,需要保证每个合数只被标记一次。
注意观察代码的不同:
for (int i=2;i<=n;i++)
{
if (!flag[i]) prime[++cnt]=i;
for (int j=1;j<=cnt;j++)
{
if (i*prime[j]<=n) flag[i*prime[j]]=true;
if (i%prime[j]==0) break; //重点!保证不被重复标记
}
}