线性筛选素数法

来源:互联网 发布:店铺模板复制软件 编辑:程序博客网 时间:2024/06/03 17:57

何为线性筛法,顾名思义,就是在线性时间内(也就是O(n))用筛选的方法把素数找出来的一种算法,没用过线性筛素数法的人可能会奇怪,用遍历取余判定素数不是也是线性时间的吗,没错,但是确切的说线性筛法并不是判定素数的,而是在线性时间内求出一个素数表,需要判定是否是素数的时候只要看该数是否在表内就可以瞬间知道是不是素数。
比如想求10000以内的素数,定义表int a[10000],进行线性筛选后,a[n]的值就代表n是不是素数,a[n]如果是1,就代表n是素数,a[n]如果是0,就代表n不是素数,这就是查表。再判定其他的素数也是一样,不用再做任何计算。
而如果用遍历取余,那么每判定一个数都要从头开始再遍历一遍,而线性筛法只在开始一次性运算完,以后只要查表即可,查表通常只需要1条语句。所以如果你的程序从始至终只需要判定那么几次素数那么用遍历取余即可,但是如果需要多次判定素数,而且这个数还不是很小的话,那么线性筛法就会体现出巨大的优越性来。
线性筛法的核心原理就是一句话: 每个合数必有一个最大因子(不包括它本身) ,用这个因子把合数筛掉。 还有另一种说法(每个合数必有一个最小素因子,用这个因子筛掉合数,其实都一样,但是我觉得这种方法不太容易说明,这种方法我会在最后给出简略说明)。这个很容易证明:这个小学就知道合数一定有因子,既然是几个数,就一定有最大的一个。最大因子是唯一的,所以合数只会被它自己唯一的因子筛掉一次,把所有合数筛掉后剩下的就全是素数了。
先假设一个数i,一个合数h,i是h最大的因数,h显然可能并不唯一(例如30和45的最大因数都是15)。那么如何通过i知道h呢,h必然等于i乘以一个比i小的素数。先来说这个数为什么一定要比i小,这很显然,如果是i乘上一个比它大的素数,那么i显然不能是h最大的因子。再来说为什么要是素数,因为如果乘上一个合数,我们知道合数一定可以被分解成几个素数相乘的结果,如果乘上的这个合数x=p1*p2*……,那么h = i * x = i * p1 * p2……很显然p1* i也是一个因数,而且大于i。所以必须乘上一个素数。
比i小的素数一定有不少,那么该乘哪一个呢,既然h不唯一,那么是不是都乘一遍呢?很显然不行,虽然h不唯一,但全乘一遍很显然筛掉的数的数量远远超过合数的数量。我们先给出结论:
任意一个数i = p1*p2*……*pn,p1、p2、……pn都是素数,p1是其中最小的素数,
设H = i * M,(M是素数,并且M<=p1),那么H的最大的因数就是i。
是的,乘上的数要小于等于i最小的质因数。
为什么呢?为了让一个合数,不被重复筛选,从而能保证线性复杂度:
假设要线性筛出[1,n]的所有质数,有一个合数
composite_num1 = pri_1*pri_2*pri_3*…*pri_n-1*pri_n;
(pri_1到pri_n递增,composite_num1<=n)
在遍历[1,n]的数时,刚好MaxDivisor1 = pri_3*pri_4*…pri_n-1*pri_n。pri_1是是它最小的质因数。
MaxDivisor如果乘以一个大于pri_3的质因数,例如pri_4,就找到另一个合数:
composite_num2 = pri_3*pri_4*pri_4*…*pri_n-1*pri_n。且MaxDivisor2 <= n。
接续遍历[1,n]中大于MaxDivisor1的部分,一定会找到这个数:
MaxDivisor2 = pri_4*pri_4*…pri_n-1*pri_n。因为n>=comsosite_num2>MaxDivisor2
然后MaxDivisor2乘以pri_3得到:
composite_num3 = pri_3*pri_4*pri_4*…*pri_n-1*pri_n*pri_3。
显然composite_num3==composite_num2,这个合数被两次筛掉,不能保证每个合数只被筛掉一次,就不能保证是线性时间复杂度。

本文给出的证明方法并不是很严格,很不严密,但是本文只是想解释线性筛素数的算法,并不是想严格证明,如果想看严格证明请看数论中的证明。另上面提到的线性筛素数的另一种说法,其实到这里读者应该差不多明白了,任何一个合数都可分解为一个素数和另一个数(不一定是素数还是合数)的乘积。我们既然找到了这个合数最大的因数,那么根据上面结论里另一个乘上的素数必然就是他的最小素因数。另一种说法只不过是换一个说法罢了。
最后我们就可以得出结论:对于每一个数i,乘上小于等于i的最小素因数的素数,就得到以i为最大因数的合数。设有一个数t,只要将所有以比t小的数为最大因数的合数筛去,那么比t小的数里剩下的就只有素数了。这就是线性筛法求素数的方法。

………………………………………………………………………………..2016.12.24更新……………………………………………………………..

想到了一种更好的理解思路:任何一个合数都可以分解成若干个质数的乘积 :
composite_Number = Pri_1*Pri_2*Pri_3…*Pri_n-1*Pri_n
Pri_1到Pri_n一次递增。
假设当前知道某个合数 composite_Number2 = Pri_3*Pri_4*…*Pri_n
composite_Number2*Pri_1 和composite_Number2*Pri_2都是以composite_Number2为最大因数的合数。Pri_1、Pri_2都是小于composite_Number2的最小素因数:Pri_3

#include <iostream>#include<algorithm>#include<cstdio>#include<cstring>using namespace std;#define N 20int flag[N+1],prime[N+1];void GetPrime(){    int sum_pri=0;//初始化没有素数    memset(flag,0,sizeof(flag));    for(int i=2; i<=N; i++){            if(flag[i]==0){            prime[sum_pri++]=i;            }       for(int j=0; j<sum_pri && prime[j]*i<=N; j++){           flag[prime[j]*i]=1;           if(i%prime[j]==0) break;//prime[j]是i的最小素因数,      }      }      for(int i=0;i<sum_pri;i++) cout<<prime[i]<<" ";       cout<<endl;}int main(){    freopen("output.txt","w",stdout);    GetPrime();    return 0;}//n个数,求第i个数之前有多少个质数int table1(int n){  //素数打表    memset(vis,0,sizeof(vis));    int cnt=0;    for(int i=2;i<=n;i++)    {        if(!vis[i]){            p[i]=++cnt;//p[i]存放0到i质数的个数            for(int j=i;j<=n;j+=i) vis[j]=1;        }    }for(int i=2;i<=n;i++)if(p[i]==0) p[i]=p[i-1];}
0 0
原创粉丝点击