数论-素数筛

有时候我们需要判断大量的数是否是素数,如果一个一个的判断时间效率太低了,怎么办呢?

我们需要学会用Eratosthenes筛法构造1-n的素数表。

输入:整数n。

输出:1-n的所有素数。

运行结果:

数论-素数筛_第1张图片

筛法的思想特别简单:对于不超过n的每个非负整数p,删除2p,3p,4p,...,当处理完所有数之后,还没有被删除的就是素数。如果用vis[i]表示i已经被删除,筛法的代码可以写成:

void eratosthenes()
{
    //对于每个不超过n的非负整数p,删除2p,3p,当处理完所有
    //数后,还没有删除的就是素数。
    int i, j;
    bool vis[maxn];
    memset(vis, false, sizeof(vis));
    vis[1] = true;
    for(i = 2; i < maxn; i++)
        for(j = i*2; j < maxn; j+=i)
           vis[j] = true;
}

尽管可以继续改进,但这份代码已经相当高效了。为什么呢?给定外层循环变量i,内层循环的次数是 n/i - 1< n/i 。这样,循环的总次数小于n/2 + n/3 + ....n/n = O(nlogn).这个结论来源于欧拉在1734年得到的结果:1+1/2+1/3+...1/n= ln(n+1) + y.其中欧拉常数y约等于0.577218

这样低的时间复杂度允许在很短的时间内得到10^6以内的所有素数。

下面来改进这份代码。

首先,在对于不超过n的每个非负整数p中,p可以限定为素数-只需在第二重循环前加一个判断if(!vis[i])即可。另外,内层循环也不必从i*2开始-它已经在i=2时被筛掉了,改进后的代码如下:

void improvement_eratosthenes()
{
    //改进
    int i, j, m;
    bool vis[maxn+5];
    memset(vis, false, sizeof(vis));
    vis[1] = true;
    m = sqrt(maxn + 0.5);
    for(i = 2; i <= m; i++)
        if(!vis[i])   //可以选素数
            for(j = i*i; j <= maxn; j += i) //不必从 i*2 开始, i=2时删掉了
                vis[j] = true;
}

这里有一个有意思的问题,给定的n,c的值是多少呢?换句话说,不超过n的正整数中,有多少个是素数呢?

素数定理:π(x)~x/lnx

其中,π(x)表示不超过x的素数的个数。上述定理的直观含义是:它和x/lnx比较接近。

 

 

 

 

你可能感兴趣的:(算法)