素数筛(彻底理解)

  • Post author:
  • Post category:其他



一.什么是素数


长话短说:

因数只有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



版权声明:本文为YSJ367635984原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。