素数筛(彻底理解)

一.什么是素数
长话短说:因数只有1和本身的正整数,(但素数不包含1). 比如 2 ,3,5,7…
二.如何判断一个数是素数
用1中的定理,也就是说,如果某个大于1的正整数与任何小于等于它的正整数的最大公因数都是1,则为素数

咳咳咳…扯远了,我们的重心不在这里,重点在下面:
三.什么是素数筛
首先,给你一个区间的正整数,让你求在这段区间内有哪些素数?
这里,我们就可以用“筛选的方法”筛去不是素数的正整数(也就是合数)剩下的就是素数了,但不同的筛法效率也是不一样的

1. 最直接的筛法
获取1到n以内的所有素数

int prime[200000]={0},c=0;  //存储素数的数组,数组里面素数的个数
void getPrime(int n)
{
    prime[++c] = 2;    //2肯定是素数
    for(int i=3;i<=n;i+=2)   //素数肯定是奇数,所以i = i+2
    {
        int flag = 0;  //判断i是不是素数标记
        for(int j=2;j*j<=i;++j)   //判断i是不是还有其他因数
        {
            if(i%j==0)  //如果i还有其他因数,则不是素数
            {
                flag = 1;  //标志i不是素数
                break;     //退出对i判断的循环
            }
        }
        //如果执行了上面代码flag还是0,说明i就是素数
        if(!flag) prime[++c] = i;
    }
}

这种方法甚至称不上是筛,压根就是暴力枚举。。复杂度O(n*sqrt(n))
评价:基本上不推荐

2. 埃氏筛
又称为埃拉托斯特尼筛法,是一种比较古老的筛法,我们直接来看它的筛法原理
准备一个判断是否素数的数组

bool isPrime[20000];

初始化:假设 2 - n都是素数。(1不是,我们先排除)

for(int i=2;i<=n;++i)
        isPrime[i] = true;

代码如下:

void getPrime(int n)
{
    for(int i=2;i<=n;++i)
        isPrime[i] = true;  //假设2-n都是素数
    for(int i=2;i<=n;++i)   //遍历2-n里面所有数
        if(isPrime[i])      //如果i是素数
            //i是素数的话,那么i的倍数肯定就不是合适
            //即 i*2,i*3 .....i*j肯定不是素数,注意边界i*j<=n
            for(int j=i;i*j<=n;++j)
                //n以内,且是i的倍数的数肯定不是素数,设为false
                isPrime[i*j] = false;
}

这是一种比较古老的素数筛选法,时间复杂度O(n*log(logn))
评价:一般题目都可以使用
3. 欧拉筛(线性筛)
欧拉筛基本上是埃氏筛的优化升级版,为什么这么说?
我们来看一个问题:
比如我要筛选 1 - 20以内所有的素数,就以埃氏筛来完成
在这里插入图片描述
我们发现,每次筛出去一个素数的倍数时存在重复筛出的操作,比如 2 * 3,和3 *2,也就是说 6 是素数2的倍数也是素数3的倍数,我们筛了两次,这就重复做了无用功。所以,我们以此来寻求优化的算法方案,这时候我们再看看 欧拉筛(也叫线性筛
《1》 我们定义一个保存素数的数组

int prime[20000];

《2》 我们定义一个记录素数数组内素数个数整型

int c = 0;

《3》我们定义一个用来判断某个数是否已经被访问的数组

bool isVisit[20000]; //默认都是false
void EulerSevie(int n)
{
    for(int i = 2;i <= n; ++i)//老规矩,遍历区间
    {
        if(isVisit[i] == false) //如果这个数未被访问,则是素数
            prime[++c] = i;     //将素数保存在素数数组里面,计数+1
        //下面for循环及里面的语句才是这个算法的精髓,我们下面细讲
        for(int j = 1;j <= c && i * prime[j] <= n; ++j)
        {
            isVisit[i * prime[j] ] = true;
            if (i % prime[j] == 0)
                break;
        }
    }
}

注意!!!,下面的图解有一点问题,在i=4的时候,由于4%2(素数)已经退出了第二层循环,所以不存在对4*3的visit赋值情况,而对4*3的visit情况赋值已经在i=6的时候对6*2赋值了true,因为6*2=3*4(感谢某大佬的指错)

在这里插入图片描述

  1. (这是当i为素数的情况下) 欧拉每次得出素数的时候,再进入循环,根据此时素数数组的个数来筛选掉此时i的倍数,而且这个倍数一定是素数(由prime可以得知)
    2.(当i不是素数的时候) 此时的i一定被标记为true,也就是 i 之前的素数将其标记为true,这样子就避免了非素数 i 进入第一个if而被存储在素数数组里面(至于为什么被标记为true自己笔写一遍就晓得了,不多说)
    3.最后一步有一个(i % prime【j】==0) 首先此时的 i 不可能为素数,因为素数模其他素数不可能被整除,也就是说,这个i一定是合数,
    4.理解一下最后一个跳出循环,如果prime【j】是i的倍数的时候,也就是说
    i = m * prime【j】, …①
    假如没有这个break条件,则++j,也就是算到了
    i * prime【j+1】这里了
    把①式子带入下面式子可以知道此时为
    m * prime【j】* prime【j+1】
    结合欧拉筛的定理,其是根据最小素数因子进行筛选,也就是说prime【j】
    是最小素数因子,如果不break的话,可能会出现埃氏筛那种重复操作的无用功,比如:
    i = 9 的话,此时prime【1】=2,prime【2】=3,prime【3】=5,prime【4】=7;
    本来是当 j=2 的时候跳出循环,但是却没有,那么j = 3
    则 9 * 5 = 3 * 3 * 5 ,该统计在 i = 15 的时候也会统计,也就是当 i = 15 时候,j = 2 ,prime【2】 = 3,也统计了 3 * 15 = 9 *5 ,明显,重复统计,所以要及时break;

欧拉筛: 时间复杂度O(n),普遍使用的素数筛
评价:普遍使用

PS:至于数组定义多大,int还是long long int,完全取决于题目要求
本人讲的可能不好,实在是实力有限,O(∩_∩)O哈哈~,祝大家学业更上一层楼!!!
拿下面数论比赛练练手吧:

https://vjudge.net/contest/377796#problem/E

  • 116
    点赞
  • 361
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌晨小街

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值