素数的一般筛法与线性筛法

来源:互联网 发布:中国移动的4g网络制式 编辑:程序博客网 时间:2024/06/05 17:29

如果不是对大素数进行判断的话,素数的打表是一个经常涉及到的内容。现在说下一般的筛法和优化后的“线性筛法”

一般的筛法(来自百度,已经知道的人可以跳过这一段):

用筛法求素数的基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。

如:

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

1不是素数,去掉。剩下的数中2最小,是素数,去掉2的倍数,余下的数是:
3 5 7 9 11 13 15 17 19 21 23 25 27 29
剩下的数中3最小,是素数,去掉3的倍数,如此下去直到所有的数都被筛完,求出的素数为:
2 3 5 7 11 13 17 19 23 29

代码:
#include <iostream>#include <stdio.h>#include <stdlib.h>#include <memory.h>using namespace std;const int maxn = 2e+8;bool prime[maxn];int ans[maxn];int cnt = 0;typedef long long LL;void make_prime(){    memset(prime,true,sizeof(prime));    prime[0] = prime[1] = false;    for(LL i = 2; i < maxn; i++)    {        if(prime[i])        {            ans[++cnt] = i;            //为什么k从i*i开始?而(i - 1) * i 以及之前的项为什么不筛?            //目前只对(i - 1)*i这一项进行说明,其他的应该类似            //分两种情况:1.(i - 1)为素数,那么(i - 1) * i 在对(i - 1)筛的时候就已经筛过了            //2.(i - 1)为合数,那么一定能找到小于等于sqrt(i - 1)的素数j,j在筛选的时候能够将(i - 1)筛去,            //(举一个特例,筛去5的倍数时从25开始,而20是一个合数,20 = 4 * 5,所以在筛2的倍数时将4筛去,同样4*5在筛2的时候一定会被筛去)            for(LL k = i * i; k < maxn; k += i)                prime[k] = false;        }    }    return;}int main(){    freopen("out.txt","w",stdout);    make_prime();    for(int i = 1; i <= cnt; i++)        cout<<ans[i]<<endl;    int a = 13;    return 0;}

乍一看,很多人认为这种筛法效率应该不差,和普通的试除法相比确实运行速度上要快很多。
但是大家可以试一下,设置maxn = 2e+8,在我的机器上要运行36s。
为什么对于稍微大些的数据,筛法还是无能为力呢?因为在程序运行时,会产生大量重复计算。
比如拿15 来说 在将3 判定素数时,将15判为合数,而在将5判定为素数,又一次将15判定为合数。很容易理解,在程序运行时,会产生大量不必要的计算。
下面说下优化后的筛法:
<a target=_blank href="http://blog.csdn.net/dinosoft/article/details/5829550">一般筛法求素数+快速线性筛法求素数</a>
线性筛法参考了上述博文 含证明

代码:
#include <iostream>#include <stdio.h>#include <stdlib.h>#include <memory.h>using namespace std;typedef long long LL;const LL maxn = 2e+8;int prime[maxn] = {0};//基于0LL num_prime = 0;bool isNotPrime[maxn] = {1,1};//除了0,1外其余都是素数int main(){    freopen("out2.txt","w",stdout);    for(LL i = 2;i < maxn;i++)//依次枚举各个数字    {        if(!isNotPrime[i])            prime[num_prime++] = i;        //进行线性筛除        for(LL j = 0;j < num_prime && i * prime[j] < maxn;j++)        {            isNotPrime[i * prime[j]] = 1;//把小于 i 的素数 和 i 的乘积置为合数这里可以分为两种情况 1.i是素数  2.i不是素数            if(!(i % prime[j]))//只筛除不大于 其最小质因数 与 i 的乘积                break;        }    }    for(LL i = 0;i < num_prime;i++)    {        if(0==i % 50)            cout<<endl;        cout<<prime[i]<<" ";    }    return 0;}

这段代码实际上用到了唯一因子分解定理,即合数a仅能以一种方式写成如下形式:
a = p1^e1 * p2^e2 * p3^e3 *....*pn^en (p1 < p2 < ... < pn)
可能很多人认为这显然成立,但还是有实际应用价值。
程序:每个数进入循环时判断是否是素数。之后进行筛除(除去合数)
和一般的筛法不同的是,它并不是将数的所有整数倍全部筛去,程序只筛除不大于 其最小质因数 与 i 的乘积
对于循环时的i来说,可以分为两种情况:
I.i是素数   II.i不是素数
1.若i是素数,那么 i * (小于 i 的素数)得到的合数在之前肯定没有筛去,所以不会发生重复筛除。
2.若i是合数,假设i = p1^e1 * p2^e2 * p3^e3 *....*pk^ek,那么只筛去小于p1 的质数与 i 的乘积这样就避免了重复筛除。 简单证明:设这个数(将要筛除的数)为 x=p1*p2*...*pn, pi都是素数(1<=i<=n)  ,  pi<=pj ( i<=j ) 第一个能满足筛除 x 的数  y 必然为 y=p2*p3...*pn(p2可以与p1相等或不等),而且满足条件的 y 有且只有一个。所以不会重复删除。 这样是否能筛除全部的合数? 参看<a target=_blank href="http://blog.csdn.net/dinosoft/article/details/5829550">http://blog.csdn.net/dinosoft/article/details/5829550</a>

这样优化后的算法,设置maxn = 2e+8时,11s就能得出结果
()


0 0
原创粉丝点击