素数

从最基础的讲起

素数,也称质数,指大于1,且不被除1与它本身以外的其它数整除的数。

朴素的求素数表的方法是直接枚举每个数,判断它是否是素数。

而判断x是否为素数的方法,最朴素的是枚举2x-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; //加入素数表
}

从代码可以看出,如果要求n以内的所有素数,则时间复杂度为O(n^{2})

改进判断素数的方法

容易发现,若na取模的结果为0,那么因为a*\frac{n}{a}=n,则n\frac{n}{a}取模的结果也等于0;这即是说,判断前\sqrt{n}个数与判断后\sqrt{n}个数是等价的(\sqrt{n}*\sqrt{n}=n)。那么,我们只用枚举2\sqrt{n}即可,时间复杂度降为O(n\sqrt{n}).。

代码是差不多的,只是枚举的上限变了:

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;
}

其它判断素数的方法

费马小定理:如果p为素数且ap不等于0,那么a^{p-1}\equiv 1\left (mod p)

\equiv表示同余,可以理解为a^{p-1}p取模的值为1)

利用这一性质,我们可以随机选取几个p,利用快速幂判断余数是否都为1,若否,则不为素数。算法复杂度为O(logp)

另外也有一种方法更为简单。首先,如果一个数是偶数,那它一定不为素数;而如果数n为奇合数,那么至少存在\frac{n-1}{2}个能证明它为合数的数,亦即是说,每选一个数出来约有\frac{1}{2}的概率检验成功,那么选k个数检验失败的概率为\frac{1}{2^{k}},出错概率极小。时间复杂度为O(k)

当然,上述两种都是随机化算法,有一定概率出错,一般是不需要用的。

改进筛选素数的方法

素数筛法有两种。

根据素数的唯一分解定理:任意一个数a都能分解为n=\prod p_{i}^{a_{i}},其中p_{i}为质因数。我们可以在筛选素数的时候标记合数。

(唯一分解定律也用来分解质因数)

flag的布尔型数组标记合数,在枚举到某个数时,除了用flag判断其是否为素数,还要与素数表里的其它素数相乘标记后面的合数。

可以证明:若枚举到i为止,i仍未被标记,那么i为素数。(因为不可能从更大的数标记到更小的数)

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;
}

另一种是时间复杂度为O(n)的欧拉线性筛。为了保证高效率,需要保证每个合数只被标记一次。

注意观察代码的不同:

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; //重点!保证不被重复标记
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值