线性筛(数学原理)

线性筛几年前理解过一次,有点模糊。今天重新理了一下,感觉这应该算非常容易理解的一种思路。

问题:找出一个方法,在O(n)的时间内找到1-n内所有的素数

面对这个问题我们可以先想简单的方法,然后层层递进:

方法1:首先所有合数都可以拆成一个质数和另一个非1的数的乘积,即:合数x=prime[j]*i ;其中x表述一个合数(x<=n),prime[j]表示第j个质数,i就是一个非1的数。那么现在我们可以分别遍历prime[j]和i来筛出所有的合数,就是一个双重循环。中间可以加上prime[j]*i<=n来节省时间复杂度。但应该还是O(nlogn)

ps:有人可能会注意到,这tm不是用结果来找结果吗,不急,这只是引一下思路,我们在方法1上做一些动作来优化。

方法2:总体和方法1一样,不过中间加了一个限制条件:在遍历i和prime[j]时加个判断,对于某个数i,我们只遍历到i的最小质数因子即可。即:prime[j]<=i的最小质因子。

证明:对于一个合数x,x= a^(a') * b^(b') * c^(c') * d^(d') * ... ,其中a那么对于合数x,一定有x/a>=a。因为x是合数,所以质因子数量至少为2,而x又是最小质因子,自然x/a>=a了。然后我们拿出最小质因子a对应成我们方法1中的prime[j],i就是x/prime[j],有prime[j]<=i。由于每个合数都会有这样一种分解。那么我们就可以使用方法2去筛出每个合数。

怎么用程序实现:有人可以发现,方法2还是用结果来找结果。但现在不一样了,我可以用之前的结果找到之后的结果,有点动态规划的感觉。具体化一点,就是说假如我已经筛出了i之前的所有质数。并且i之前我都用方法2筛了大于i的某些合数。那么对于i+1,若i+1是合数,设i+1的最小质因子为prime[j]那么必有i+1/prime[j]<=i,又因为我们用方法2更新,那么i+1一定会在之前某次被筛为合数。

时间复杂度:为什么这就是线性筛了呢?可以发现,唯一会造成非线性的地方就是内层循环,而内层循环就是用于筛合数的。那么我们只要证明每个合数都仅仅会被筛一次,整个过程不就是线性的吗?

时间复杂度证明:每个合数x的最小质因子prime[j]都是被确定的。那么x/prime[j]也是可以确定的,即i是可以被确定的,而我们把每个i都遍历了小于i的所有素数,其中一个素数就是prime[j]。可以看出只被筛过一次。换句话说,对于任意的合数x,我都能告诉你它是在哪次,而且是唯一哪次被筛出来的。

代码:

void get_prime(int n)
{
    int visited[max_n],prime[max_n];
    int m=0;
    memset(visited,0,sizeof(visited));
    for(int i=2;i<=n;i++)
    {
        if(!visited[i]) prime[m++]=i;
        for(int j=0;j

你可能感兴趣的:(数论,算法,c++,素数筛,线性筛)