ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

素数筛法

2019-02-12 22:45:14  阅读:242  来源: 互联网

标签:筛法 int 合数 29 素数 倍数 筛除


这篇博客是按照我学习素数的顺序写的,算法也是一步步推进,一步步优化的,想找最高效的算法可以直接翻到欧拉筛。这是我的第一篇博客,欢迎各路大神指正错误以及提供建议。

素数定义

质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。
0和1既不是质数,也不是合数。
2是最小的素数,也是唯一一个偶素数。

在比赛中,经常碰到素数相关的题目,下面介绍几种常见的素数判断方法。

基本方法

从2到a-1进行枚举,如果存在i使a能被i整除(a%i==0成立),则a为合数。如果不存在这样的i,则a为质数。
以下代码功能为:输入一个数,判断是否为质数。

#include <bits/stdc++.h> //“万能头文件”
using namespace std;
typedef long long ll;//为编码方便,进行typedef
bool isprime(ll a)
{
    ll i;
    for(i=2;i<a;i++)
        if(a%i==0) break;
    if(a==i) return true;//如果在2到a-1都没有找到能整除a的值,则a为质数
    else return false;//循环中找到了能整除a的值,break过,导致a不等于i
}
int main()
{   //主函数
    ll a;
    cin>>a;
    if(isprime(a)) cout<<"YES"<<endl;
    else cout<<"NO"<<endl;
    return 0;
}

时间复杂度:对每一个数,isprime函数最多运算a-2次,时间复杂度接近\(O(a)\)。

初步优化

内容

实际上,我们只需要枚举\(\sqrt a\)次即可完成判断,可将时间复杂度优化到\(O(\sqrt a)\)。

原理

利用反证法,假设一个数a,在2到\(\sqrt a\)之间没有找到a的因数,却在\(\sqrt a+1\)到\(a-1\)之间找到一个a的因数i,则必存在a的另一个因数\(j=\frac{a}{i}\)。因为\(i>\sqrt a\),可得\(\frac{1}{i}<\frac{\sqrt a}{a}\),可得\(j=\frac{a}{i}<\sqrt a\),与假设矛盾。

bool isprime(ll a)
{
    ll i;
    if(a<=1) return false;//防止某些题目出0、1一类的数据 
    for(i=2;i*i<=a;i++)
        if(a%i==0) return false;
    return true;
}

素数筛法

现在我们要输出所有n以内的素数,请在1s内完成。(n<=106
如果我们仍然按照刚才的方法来解决这个问题,由于每一次询问的时间复杂度为\(O(\sqrt n)\),那m次询问的总复杂度会达到O(n1.5),运算量的数量级为109,那么等待我们的就会是——
Time Limit Exceeded.(时间超限)
所以我们需要更高效的做法,于是就有了素数筛选法。

埃氏筛

原理

当一个数i是素数时,i的所有倍数必然是合数,就可以从素数集中划去。如果i已经判断为合数了那就不必划掉i的倍数了,因为i的倍数已经被i的素因子划掉了。

做法

我们可以建立一个2到n的集合,把最小数2保留,2的倍数划掉。然后寻找最小的下一个数3,由于它没有被划掉,它也就无法被比他小的数整除,因此它是素数。此时,再划掉3的所有倍数。再找下一个数5(4被2划掉了,因此4是合数,无需考虑),划掉5的所有倍数。这样反复操作,就能枚举n以内的素数。

模拟

我们可以模拟一下寻找30以内的素数(加粗代表新划去的数):

  1. 建立集合
    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
  2. 筛除2的倍数:4 6 8 10 12 14 16 18 20 22 24 26 28 30
    2 3 5 7 9 11 13 15 17 19 21 23 25 27 29
  3. 筛除3的倍数:6 9 12 15 18 21 24 27 30
    2 3 5 7 11 13 17 19 23 25 29
  4. 4为合数跳过4(之后合数省略)
  5. 筛除5的倍数:10 15 20 25 30
    2 3 5 7 11 13 17 19 23 29
  6. 筛除7的倍数:14 21 28
    2 3 5 7 11 13 17 19 23 29
  7. 筛除11的倍数:22
    2 3 5 7 11 13 17 19 23 29
  8. 筛除13的倍数:26
    2 3 5 7 11 13 17 19 23 29
  9. 筛除17、19、23、29的倍数:均不含小于30的倍数
    ... ...
  10. 筛除到30,算法结束,30以内素数为:
    2 3 5 7 11 13 17 19 23 29

通过模拟我们发现从7开始没有筛除掉任何新的素数,因此我们可以进行优化。
事实上我们只需要枚举到\(\sqrt n\)。
原因:我们每次划去的数只会是i的倍数中不含小于i的素因子的数,所以这些数必然包含大于等于的i素因子,所以这些数一定都是大于等于i2的数。

代码实现

建立一个比较大的bool数组isprime[],其大小取决于数据规模,比如n+5。为方便我们将素数记为false(因为bool全局变量初始值为false)从2开始,从小到大筛选每一个数i,如果i是素数,就将所有的i的倍数置为true,即筛掉所有该素数的倍数,否则不做任何处理,直接筛选下一个数。

#include <iostream>
#include <cstdio>
const int N=1e7+5;
using namespace std;
bool isprime[N]={true,true};//将0、1筛去
int p[N];
int main()
{
    int n;
    cin>>n;
    //筛法开始
    for(int i=2;i*i<=n;i++)
    {
        if(!isprime[i])
        for(int j=i+i;j<=n;j+=i)
            isprime[j]=true;//筛去j的倍数
    }
    int k=1;
    p[0]=2;
    for(int i=3;i<=n;i+=2)//因为除2外无偶素数,可以只遍历奇数
        if(!isprime[i])
            p[k++]=i;//将素数按照顺序存入数组中
    //筛法结束
    for(int i=0;i<k;i++)
        printf("%d ",p[i]);
    return 0;
}

这样的时间复杂度是O(nloglogn)。其实我不知道是怎么算出来的
相比O(n1.5)的暴力做法快了很多,但我们还有更高效的做法。

欧拉筛

优化原理

由刚刚埃氏筛的模拟我们不难发现,许多合数被筛选了多次,而我们在埃氏筛法的基础上进行优化,让每个合数只被它的最小质因子筛选一次,避免重复筛选,可以得到接近O(n)的时间复杂度。
先上代码:

#include <iostream>
#include <cstdio>
const int N=1e7+5;
using namespace std;
bool isprime[N]={true,true};
int p[N];
int main()
{
    int n;
    cin>>n;
    //筛法开始
    int k=0;
    for(int i=2;i<=n;i++)
    {
        if(!isprime[i]) p[k++]=i;//判到哪打到哪
        for(int j=0;p[j]*i<=n&&j<k;j++)
        {
            isprime[i*p[j]]=true;
            if(i%p[j]==0) break;//欧拉筛核心
        }
    }
    //筛法结束
    for(int i=0;i<k;i++)
        printf("%d ",p[i]);
    return 0;
}

核心代码

接下来解释一下欧拉筛最为核心的一段代码

for(int j=0;p[j]*i<=n&&j<k;j++)
{
    isprime[i*p[j]]=true;
    if(i%p[j]==0) break;
}

在欧拉筛里,我们依然只筛除质数的倍数。由于已经打了一部分质数表,我们可以筛除p[j]i(p是质数表)。判断条件中的p[j]i<=n保证了筛除的数不会超出n的范围,节省时间也防止了数组下标向上越界,j<k保证j不超出当前质数表个数k。

第三行就是将素数的i倍筛除,标为true。

第四行是欧拉筛的核心也是最难理解的部分。我的个人理解就是我们让每个合数只被他的最小质因子筛除一次,如果i已经包含了p[j]这个因子,就有两种情况:

  1. i=p[j] ,此时p[j]一定是p中最后一个数,跳出循环
  2. i是p[j]的倍数,此时存在整数\(k=\frac{i}{p[j]}\),i可表示为\(k*p[j]\)。如果不跳出,我们下一个准备筛除的数就是\(p[j+1] *i\),即\(p[j+1]*k*p[j]\),这个数的最小质因子是p[j],不应被p[j+1]筛除。以此类推,之后筛除的所有数都有最小质因子p[j],不应被更大的质因子筛除,故跳出循环。

米勒罗宾素数检测法

基于随机算法,可以在O(logn)内判断一个数是否是素数,但存在一定的误差。
在需要判断的素数大小过大的时候,依靠素数筛法会爆内存,可以使用这种方法。
因为我没看懂就不具体写了,学会再回来更新。

标签:筛法,int,合数,29,素数,倍数,筛除
来源: https://www.cnblogs.com/kaixinqi/p/10367282.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有