• 筛法求素数


    筛法求素数

    前言

    素数(质数):除了1和它本身以外不再有其他因数(能被整除的数)
    合数:除了能被1和本身整除外,还能被其他数整除的数
    互质:公约数只有1的两个整数

    题目:判断1-n的范围内有多少个素数?oj练习
    判断一个数是否为素数,一般会想到以下代码

    //时间复杂度O(n*sqrt(n))
    bool sushu(int n){
        if(n<=1) return 0;
        for(int i=2;i<=sqrt(n);++i){
            if(n%2==0) return 0;
        }
        return 1;
    }
    

    当n取很大时,每判断一个数 i(1<=i<=n)是否为素数,都要枚举sqrt(i)次,时间复杂度接近O(n^2),
    提前建立一张素数表,再通过查表的方式来判断素数。而这里打表的算法,就需要用到筛法,把表中不是素数的筛去
    这里介绍两种常用的筛法:埃氏筛、欧拉筛(线性筛)

    埃氏筛

    埃氏筛的思想:素数的倍数都不是素数,如2的倍数(即偶数)都不是素数
    用一个prime[n] bool数组来打表,prime[i]=0表示i是素数,prime[i]=1表示i不是素数
    一开始都初始化为0,都认为是素数,然后慢慢把不是素数的筛去(prime[i]标为1)
    先标记prime[1]=1,因为1不是素数
    接着从2开始,把2的所有倍数(合数)都一个个筛去(prime[2*2]、prime[2*3]...)

    //时间复杂度O(nloglogn)
    #define ll long long
    ll n;
    bool prime[1000005]; //一开始都认为是质数(初始化为0),0代表是质数,1代表不是
    void ai_shai(){
        prime[1]=1; //1不是质数
        for(ll i=2;i<=n;++i)
            if(prime[i]==0) //如果是质数
                for(ll j=i*i;j<=n;j+=i) //从i*i开始筛选 因为2*i~(i-1)*i 之前已被2~(i-1)筛出来了
                    prime[j]=1; //质数的倍数都不是质数
    }
    

    上述的从i*i开始筛选可以理解为,比如轮到3的时候,可以从3*3开始筛选,因为3*2已经被2*3筛去了

    还有为什么要判断是否为素数后再进行筛选,比如4不是素数,4的倍数8、16等之前已经被2给筛过了2*4、2*8,就会产生多余步骤

    欧拉筛(线性筛)

    埃氏筛的时间复杂度为O(nloglogn),如何更进一步的优化?
    仔细观察可发现,上面的埃氏筛也有一些重复计算,有些数会被不同的素数筛多次,比如30=3*10,30=5*6等
    所以欧拉筛的思想就是:在埃氏筛的基础上,让每个合数只被其最小的质因数筛一次
    这使得欧拉筛的时间复杂度可以达到O(n)的线性复杂度,所以欧拉筛也称线性筛

    建立两个数组,一个是bool类型的素数表prime[],用来表示prime[i]是不是素数。另一个是int类型用来存储每个素数的数组zyz[],每个素数都会成为某个合数的最小质因子
    代码如下,注释详细:

    //时间复杂度O(n)
    bool prime[1000005]; //1代表不是素数,0代表是素数
    int zyz[1000005]; //合数最小的质因子,也记录着每一个质数
    int cnt=0; //记录质数的个数
    void ol_shai(){
        prime[1]=1; //1不是质数
        for(int i=2;i<=n;++i){
            if(prime[i]==0) //如果是质数
                zyz[++cnt]=i; //i是一个素数,存入zyz数组中
            for(int j=1;j<=cnt&&i*zyz[j]<=n;++j){ //每个素数对应i倍的合数筛去
                prime[i*zyz[j]]=1; //合数i*zyz[j]的最小质因子是zyz[j]
                if(i%zyz[j]==0) break;
                //如果zyz[j]还是i的最小质因数,后面的合数i*zyz[j+1]会被zyz[j](最小质因数)筛去
            }
        }
    }
    

    其中有关break的语句,这里拿出来再解释一下
    因为zyz[j]是从小到大遍历的素数集合,如果i%zyz[j]=0,那么zyz[j]一定是i的最小质因数
    如果i%zyz[j]=0,这里设一个数k,k*zyz[j]=i,下一个循环的i*zyz[j+1]可以分解为k*zyz[j]*zyz[j+1],其中最小质因数为zyz[j],所以会在i=(k*zyz[j+1])时被zyz[j]筛选出来

    举个例子,i=10,j=1,zyz[1]=2,10%2=0,如果没有break,而是继续循环 j=2,zyz[2]=3,i*zyz[2]=10*3=5*2*3=2*15,最小质因数为2(zyz[1]),会在i=15的时候,被zyz[1]=2筛出来
    这个关键的break保证了一个合数只被其最小质因数筛出,使得O(n)的时间复杂度得以成立

  • 相关阅读:
    redis中save和bgsave区别
    scrapy生成json中文为ASCII码解决
    mysql数据库,创建只读用户
    memcached命令行、Memcached数据导出和导入
    Memcache 查看列出所有key方法
    Elasticsearch5.x 引擎健康情况
    docker容器创建MariaDB镜像
    大文本数据排序
    换行符 和回车符
    索引与文本文件
  • 原文地址:https://www.cnblogs.com/lidasu/p/11046109.html
Copyright © 2020-2023  润新知