素数筛法

来源:互联网 发布:淘宝账号无法开店 编辑:程序博客网 时间:2024/05/24 05:50

根据素数的性质,有这样的定理:

1、设 n 是一个正合数,p 是 n 的一个大于1的最小正因数,则 p 一定是素数。

2、设 n 是一个正整数,如果对所有的素数 p ≤ √n,都有 p 不整除 n,则 n 一定是素数。

1.        原始版本原理:

先把n个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一直做下去,就会把不超过N的全部合数都筛掉,留下的就是不超过N的全部质数。该方法简称“筛法”。

2.        原始版本代码实现;

#include<iostream>

#include<memory.h>

#include<cmath>

using namespace std;

const int MAX=100;//遍历范围的上限

const int sqrtnum=sqrt(MAX)+1;//上限的平方根+1

bool prime[MAX];//布尔类型,true代表是素数

int main()

{

   memset(prime,true,sizeof(prime));//先全部设为true

for(int i=2; i<=sqrtnum;i++)

//2是最小的素数,只需要一直划到sqrtnum,后面数字不可能再是该数字的因数

    {

       if(prime[i])//若该数字没有被划掉,即:该数字是素数

        {

           for(int j=i+1; j<MAX; j++)//划掉该素数后面的倍数

            {

               if(j%i==0)//是倍数

               {

                   prime[j]=false;//划掉

               }

            }

        }

    }

    for(inti=2; i<MAX; i++)

    {

       if(prime[i])

        {

             cout<<i<<endl;

        }

    }

    return 0;

}

运行结果:

2

3

5

7

11

13

17

19

23

29

31

37

41

43

47

53

59

61

67

71

73

79

83

89

97

分析:这种原始版本会重复的划掉一些数字.

3.        优化次数版本代码实现:

#include<iostream>

#include<memory.h>//将布尔数组全部初始化为true

#include<cmath>

usingnamespace std;

int main()

{

    int range;

    cin>>range;

    int num;

if(range%2==0)

//精确定位要求的范围,num是布尔数组的最大下标,2*num+3是求出的最大素数

    {

        num=(range-4)/2;

    }

    else

    {

        num=(range-3)/2;

    }

    bool prime[num+1];

    memset(prime,true,sizeof(prime));

    int sqrtnum=sqrt(range)+1;

    for(int i=0;2*i+3<=sqrtnum;i++)

    {

        if(prime[i])

        {

            for(intj=2*i+3+i;j<=num;j=j+2*i+3)//直接定位后面的倍数,无序遍历寻找

            {

                prime[j]=false;

            }

        }

    }

    int print=0;

    for(int i=0;i<=num;i++)

    {

       if(prime[i])

        {

           cout<<(2*i+3)<<"  ";

            print++;

        }

        if(print%10==0)

        {

            cout<<endl;

        }

    }

}

解析:

该优化的程序主要优化了一下几点:

1)        布尔类型数组存储的元素全是奇数且从3开始。因为一切大于2的偶数必定是合数。省去了筛偶数的时间。

2)        该程序能够精确求出3——range(包括range在内)的所有素数。

3)        在确定了小素数后,直接定位其后面所有的倍数,不是一个一个搜索。

用一个数组x[0], x[1], …来存储3,5,7,11…這些奇数,因此,x[i]中所有存储的数就是2i+3。将所有元素初始状态设置为未筛除,然后依次去掉其中的合数。考虑删除合数。从x[0],x[1]…如果x[i]未被筛出,则其为质数,删除其所有倍数,依次往下走。但x[i]中储存的值是2i+3,而2i+3的倍数在数组中的什么地方呢?因为数组中全部是奇数,所以2i+3的倍数可以表示为(2n+1)(2i+3),根据(2n+1)(2i+3) = 2n(2i+3)+2i+3 = 2[n(2i+3)+i]+3,知道x[i]对应的数(即2i+3)的倍数的位置为n(2i+3)+i。当n=1是,值为(2i+3)+i,当n=2时,值为(2i+3)+i+(2i+3),依次类推,用(2i+3)+i作为初值,删掉对应的x[],再加上2i+3,删掉对应的数,直到无数可删。因为最后的一个素数可以表示为2N+3,所以筛法求出的为2到2N+3之间的素数。

4.        快速线性筛法(欧拉筛法)

#include<iostream>

#include<memory.h>

using namespace std;

const int N=1000000;

bool flag[N+1];

int prime[N+1];

int primenum=0;

/*

flag[n] 表示n是否是素数,true是素数,false不是

prime  中是所有的素数按从小到大排列

primenum 表示素数的个数

*/

int main()

{

   memset(flag,true,sizeof(flag));//先将所有数看做素数,然后开始筛选

   for(int i=2; i<=N; i++)//遍历筛去所有最大因数是i的合数

    {

       if(flag[i])

       {

           prime[primenum++]=i;//把素数记录下来

       }

       for(int j=0; j<primenum && prime[j]*i<=N; j++)

//遍历已知素数表中比i的最小素因数小的素数,并筛去合数

       {

           flag[prime[j]*i]=false;//筛去合数

           if(i%prime[j]==0)

           {

                break;//找到i的最小素因数

           }

       }

    }

   return 0;

}

N=100000000时:2265毫秒

解析:

线性筛法的核心原理就是一句话: 每个合数必有一个最大因子(不包括它本身) ,用这个因子把合数筛掉,还有另一种说法(每个合数必有一个最小素因子,用这个因子筛掉合数)。这个很容易证明:合数一定有因子,既然是几个数,就一定有最大的一个。最大因子是唯一的,所以合数只会被它自己唯一的因子筛掉一次,把所有合数筛掉后剩下的就全是素数了。

 

先假设一个数i,一个合数t,i是t最大的因数,t显然可能并不唯一(例如30和45的最大因数都是15)。那么如何通过i知道t呢?t必然等于i乘以一个比i小的素数。先来说这个数为什么一定要比i小,这很显然,如果是i乘上一个比它大的素数,那么i显然不能是t最大的因子。再来说为什么要是素数,因为如果乘上一个合数,我们知道合数一定可以被分解成几个素数相乘的结果,如果乘上的这个合数x=p1*p2*……,那么t = i * x = i * p1 * p2……很显然p1* i也是一个因数,而且大于i。所以必须乘上一个素数。比i小的素数一定有不少,那么该乘哪一个呢,既然t不唯一,那么是不是都乘一遍呢?很显然不行,虽然t不唯一,但全乘一遍很显然筛掉的数的数量远远超过合数的数量。我们先给出结论:任意一个数i = p1*p2*……*pn,p1、p2、……pn都是素数,p1是其中最小的素数,设T 为i * M的积(显然T就成了一个合数),也就是T = i * M,(M是素数,并且M<=p1),那么T的最大的因数就是i。是的,乘上的数要小于等于i最小的质因数。

证明:

假设i可以表示为素数乘积:i = p1*p2*...pn,其中i最小的素因数是p1,设M是素数,并且大于i最小的素因数p1。设i*M=y,显然同时y=M * p1*p2*……pn因为M>p1,显然

M*p2*……pn要大于i=p1*p2*……pn,M*p2*……pn又很显然是y的一个因数,那么y的最大因数就不是i。由此说明了了上面给出的结论。

读者应该差不多明白了,任何一个合数都可分解为一个素数和另一个数(不一定是素数还是合数)的乘积。我们既然找到了这个合数最大的因数,那么根据上面结论里另一个乘上的素数必然就是他的最小素因数。另一种说法只不过是换一个说法罢了。

最后我们就可以得出结论:对于每一个数i,乘上小于等于i的最小素因数的素数,就得到以i为最大因数的合数。设有一个数t,只要将所有以比t/2小的数为最大因数的合数筛去,那么比t小的数里剩下的就只有素数了。这就是线性筛法求素数的方法。

0 0
原创粉丝点击