筛法求素数

   看通常做法:首先假设要检查的数是N好了,则事实上只要检查至N的开根号就可以了,道理很简单,假设

A*B = N,如果A大于N的开根号,则事实上在小于A之前的检查就可以先检查到B这个数可以整除N。不过在程式中使用开根号会精确度的问题,所以可以使用i*i <= N进行检查,且执行更快

    而筛法具体做法是:先把N个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一直做下去,就会把不超过N的全部合数都筛掉,留下的就是不超过N的全部质数。因为希腊人是把数写在涂腊的板上,每要划去一个数,就在上面记以小点,寻求质数的工作完毕后,这许多小点就像一个筛子,所以就把埃拉托斯特尼的方法叫做“埃拉托斯特尼筛法”,简称“筛法”。(另一种解释是当时的数写在纸草上,每要划去一个数,就把这个数挖去,寻求质数的工作完毕后,这许多小洞就像一个筛子。

  在n(n为求素数的范围)以内的数中,把2的倍数都去掉,再把3的倍数都去掉,如此,依次把第一个没有去掉数的倍数去掉,注意这个数本身不去掉.最后没被去掉的为所有的素数

#include<stdio.h>
#include<memory.h>

bool * prime(int n)
{
    bool *p=new  bool[n]; 
memset(p,n,sizeof(bool)*n);
//可以不要p[0]=p[1]=false; for(int i=2;i<n;i++) //这里i《n可以换成i*i<n if(p[i]) for(int j=i*i;j<n;j+=i) //注意这里j=i*i,很多人写成j=2*i,效率不高 p[j]=false; return p; } int main() { int n; scanf("%d",&n); bool *a=prime(n); printf("n内不包括n的素数有\n"); for(int i=2;i<n;i++) if(a[i]) printf("%d ",i); }

 这个筛法是近似线性。

检查的次数还可以再减少,事实上,只要检查6n+1与6n+5就可以了,也就是直接跳过2与3的倍
数,使得程式中的if的检查动作可以减少

参考这个:

下列是伪代码算法:

// arbitrary search limit
limit ← 1.000.000                   

// assume all numbers are prime at first                                    
                                                                            
is_prime(i) ← true, i ∈ [2, limit] 

for n in [2, √limit]:
    if is_prime(n):
        // eliminate multiples of each prime,
        // starting with its square
        is_prime(i) ← false, i ∈ {n², n²+n, n²+2n, ..., limit}

for n in [2, limit]:
    if is_prime(n): print n

可再简化为:

limit = 1000000
sieve$ = string of the character "P" with length limit

prime = 2
repeat while prime

2

 < limit
    set the character at the index of each multiple of prime (excluding index prime * 1) in sieve$ to "N"
    prime = index of the next instance of "P" in sieve$ after index prime
end repeat

print the index of each instance of "P" in sieve$

上面这个代码很好理解,下面这个就有点难理解了。

#include<stdio.h>
#include<memory.h>
#define mr 10
bool notp[mr];//素数判定

int  pr[10000],pn,ans;//pr存放素数,pn当前素数个数。

void  getprime()
{
    pn=0;
    memset(notp,0,sizeof(notp));
    for(int i=2;i<mr;i++)
    {
         
        if(!notp[i]) pr[pn++]=i;  //注意这里是后缀,pr[0]存放第一个元素,若改成前缀,则pr[1]存放第一个
        for(int j=0;j<pn&&pr[j]*i<mr;j++)
        {
            notp[pr[j]*i]=1;
            if(i%pr[j]==0) break;
        }
    }
}

int main()
{
    getprime();
     for(int i=0;i<pn;i++)
         printf("%d    ",pr[i]);
}

利用了每个合数必有一个最小素因子

每个合数仅被它的最小素因子筛去正好一次。所以为线性时间。

代码中体现在:

if(i%pr[j]==0)break;

pr数组中的素数是递增的,当i能整除pr[j],那么i*pr[j+1]这个合数肯定被pr[j]乘以某个数筛掉。

因为i中含有pr[j],pr[j]比pr[j+1]小。接下去的素数同理。所以不用筛下去了。

在满足i%pr[j]==0这个条件之前以及第一次满足改条件时,pr[j]必定是pr[j]*i的最小因子。

三、结合线性筛素数算法的优化算法

基于这个线性筛素数算法,我们可以很容易地得到某个数的最小素因子。

因为当i%pr[j]!=0的时候,最小素因子pr[j]与i互质,满足积性函数的条件,可以直接得到f(i*pr[j])=f(i)*f(pr[j]).

不过当i%pr[j]==0时我们必须根据该积性函数本身的特性进行计算.或者在筛的同时保存并递推些附加信息.总之要O(1)求得f(i*pr[j])及完成递推附加信息.

下面的两个例子是欧拉函数phi和约数个数.这两个是最常用和最有优化价值的。

利用上面的性质都可以很容易地把前n个用O(n)时间推出来.

当然,利用这个性质还可以对其他积性函数进行优化,这里仅介绍两个常用和有优化价值的。

1)欧拉函数(phi)

传统的算法:

对于某素数p且p|n(n能整除p)

if( (n/p) % i == 0 ) phi(n)=phi(n/p)*i;

else phi(n)=phi(n/p)*(i-1);

这个传统算法的性质正好用在筛素数算法中.

p为n的最小素因子,当n/p包含该因子p,则phi(n)=phi(n/p)*i;否则phi(n)=phi(n/p)*(i-1);

p即pr[j], n/p即i, n即i*pr[j]了.

2)约数个数(divnum)

约数不能像phi那么自然,但还是有不错的方法.

约数个数有个性质

divnum(n)=(e1+1)*(e2+1)...(ei表示n的第i个质因数的个数.)

传统方法就是对每个数分解质因数,获得各因数个数再用上式.

开一个空间e[i]表示最小素因子的次数

这次说直接点:

筛到i 第j个素数

对于divnum

如果i|pr[j] 那么 divnum[i*pr[j]]=divsum[i]/(e[i]+1)*(e[i]+2) //最小素因子次数加1

否则 divnum[i*pr[j]]=divnum[i]*divnum[pr[j]] //满足积性函数条件

对于e

如果i|pr[j]  e[i*pr[j]]=e[i]+1; //最小素因子次数加1

否则 e[i*pr[j]]=1; //pr[j]为1次

 

 

 

 

原文地址:https://www.cnblogs.com/youxin/p/2486590.html