线性筛(数学原理)

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

问题:找出一个方法,在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<b<c<d<...,且a,b,c,d...均为质数。其实这就是质因子分解。那么对于合数x,一定有x/a>=a。因为x是合数,所以质因子数量至少为2,而x又是最小质因子,自然x/a>=a了。然后我们拿出最小质因子a对应成我们方法1中的prime[j],i就是x/prime[j],有prime[j]<=i,又因为a是x的最小的质数所以也小于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<m&& prime[j]*i<=n;j++)
        {
            visited[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值