zoukankan      html  css  js  c++  java
  • 全网一定不是最好懂的C++线性筛素数

    Part 0:概念

    先给几个概念(很重要):

    • 合数:如果(xy=z ext{且}x,y ext{为正整数}),我们就说(x,y ext{是}z ext{的合数})
    • 素数:如果数(a)合数只有(1,a),则(a)就是一个素数
    • 整除:整数(b)除以非零整数(a),商为整数,且余数为零, 我们就说(b)能被(a)整除,记做(a | b)。数学中,求一个数的余数的运算叫做取余,用(a MOD b)表示求a除以b的余数,计算机中用%
      当然,如果有(a | b),那么我们可以写成(a MOD b = 0)
    • 不含0,1的所有自然数除了素数就是合数

    Part 1:普通筛法及优化到根号

    首先,从普通筛法开始:

    #include <iostream>
    using namespace std;
    int main(){
        return 0;
    }
    

    判断一个数是不是素数,就从他的定义入手
    定义是啥?回顾一下:

    如果数(a)合数只有(1,a),则(a)就是一个素数

    再次回顾合数的概念:

    如果(xy=z ext{且}x,y ext{为正整数}),我们就说(x,y ext{是}z ext{的合数})

    我们知道,不含0的所有自然数不是素数就是合数,而我们算法的名字叫做筛素数,而素数的定义离不开合数
    干脆,我们把合数筛掉吧!
    我们从(xy=z入手)。显然,(x,y,z neq 0),可以大胆的除一下:(x = frac{z}{y})
    如果你比较熟悉开头的那些定义,你就会发现:因为(x)是个正整数,所以(z | y),也就是(z MOD y = 0)
    这个地方可以好好理解一下

    那么,我们开始填充代码:

    #include <iostream>
    using namespace std;
    int main(){
        bool flag = 1;//素数判断标签
        int n = 10;//n代表我们需要筛从1~n的素数
        for(int i = 2;i <= n;i ++){//很重要!公式中的z(也就是这里的i)要从2开始,想想为什么 
            //cout << "i = " << i << endl;
    		for(int j = 2;j < i;j ++){//很重要!想想为什么公式中的y(也就是这里的j)为什么从2开始,为什么要比i小? 
    			//cout << "进入二层循环,j = " << j << endl;
                if(i % j == 0){//如果是合数
                    flag = 0;
                    //cout << "i % j = " << i%j << endl;
                    //cout << "二层循环if,i = " << i << "j = " << j << endl; 
                    break;
                }
            }
             
            if(flag) cout << i << endl;//否则输出
            flag = 1;//小细节~自己模拟一下
        }
    }
    

    好的,代码是出来了
    可以更快吗?当然可以
    我举个例子:36。

    36的因数有:{1,36},{2,18},{3,12},{4,9},{6,6}

    把36的因数排个序:{1,2,3,4,6},{6,9,12,18,36}
    以6为分界线,我们可以把它分为了两份,请注意:
    (sqrt{36} = 6)
    哇塞!我们只需要循环到(sqrt{n})我们就可以结束了(因为因数是两两对应的(如果不考虑去重(比如49的因数表暂时定为:1,7,7,49)))
    改进一下:

    #include <iostream>
    #include <cmath>
    using namespace std;
    int main(){
        bool flag = 1;//素数判断标签
        int n = 10;//n代表我们需要筛从1~n的素数
        for(int i = 2;i <= sqrt(n);i ++){//很重要!公式中的z(也就是这里的i)要从2开始,想想为什么 
            cout << "i = " << i << endl;
    		for(int j = 2;j < i;j ++){//很重要!想想为什么公式中的y(也就是这里的j)为什么从2开始,为什么要比i小? 
    			cout << "进入二层循环,j = " << j << endl;
                if(i % j == 0){//如果是合数
                    flag = 0;
                    cout << "i % j = " << i%j << endl;
                    cout << "二层循环if,i = " << i << "j = " << j << endl; 
                    break;
                }
            }
             
            if(flag) cout << "------" << i << endl;//否则输出
            flag = 1;
        }
    }
    

    Part 2 真正的筛——埃式筛

    刚才那个算法的思想再怎么说,也是个素数
    但是,判断合数显然比判断素数更简单
    那么,为什么不把合数筛走,剩下的不就是素数了吗?
    好办法!接下来我们想下怎么筛
    其实啊,可以发现某一个不为0/1的数 × 另一个不为0/1的数 = 合数
    那么,假设有一个数(q),我们只需要筛掉(1q,2q,3q ext{……一直到}iq>n)时,接着q++,继续筛!
    上代码:

    #include <iostream>
    //代码来自https://www.cnblogs.com/Return-blog/p/12307038.html
    using namespace std;
    bool book[1000000];//素数标记盒子
    int main(){  //求1~100以内的素数
        for(int i=2;i<=100;i++)
            if(!book[i]){//因为book数组在main()外部自动填充为0,所以需要!0转换为11
                for(int j=i+i;j<=100;j+=i)//q,2q,3q,4q...
                    book[j]=1;//标记上1的就是合数了!
            }
        for(int i=2;i<=100;i++)
            if(!book[i]) cout << i << endl;//是0就输出~
        return 0;
    }
    

    哦太棒了!
    但是!请你自己模拟一下这个过程,你会发现个奇怪的过程拖慢了速度!

    当i = 2 => 2i=4,3i=6,4i=8...
    当i = 3 => 2i=6,3i=9,4i=12...
    当i = 4 => 2i=8...

    Oh No!又开始重复了!
    重复怎么办?再筛掉!


    Part 3:线性筛

    //代码来自https://www.cnblogs.com/Return-blog/p/12307038.html
    #include <iostream>
    using namespace std;
    
    bool book[20005];
    int prime[20005],pos,i,j;
    int main(){  //求1~100以内的素数
        for(i=2;i<=100;i++){
            if(!book[i]) prime[++pos]=i; //判断是否被筛过。!0(没有)的话,就记录一下,
            for(j=1;j<=pos;j++){
                if(i*prime[j] > 100) break; //范围:不让他超过i,超过就break
                book[i*prime[j]]=1; //i乘上素数得到的书就标记下,筛过了!
                if(i%prime[j]==0) break; //!!!prime数组 中的素数是递增的,当 i 能整除 prime[j],那么 i*prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉 想想为什么
            }
        }
        for(int i=1;i<=pos;i++)
            cout << prime[pos];
        return 0;
    }
    

    Oh太棒了!你终于搞到了最快的方法
    最后送你个福利:证明一下为什么:当 i 能整除 prime[j],那么 i*prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉
    首先,prime数组中的素数是递增的(这个不用说)
    因为i中含有prime[j],prime[j]比prime[j+1]小
    (i=k imes prime[j])
    那么(i imes prime[j+1]=(k imes prime[j]) imes prime[j+1])
    也就是(k’ imes prime[j])
    看不懂没关系,板子背下来就好了~

  • 相关阅读:
    在vm中安装ubuntu 11 并部署 xampp集成环境步骤
    修改xampp的mysql默认密码
    linux下重命名文件的命令
    IE6下DIV最小高度不能为0的解决方法
    linux下解压tar.gz 文件和 tar.bz2 文件命令
    linux svn 使用
    win7 下设置java环境变量
    在linux下安装svn软件rabbitvcs
    linux下开启ssh服务
    【转】DataSet与DataTable的区别
  • 原文地址:https://www.cnblogs.com/sdltf/p/12313035.html
Copyright © 2011-2022 走看看