问题重述
给定一个正整数n,请你求出1~n中质数的个数。
求解思路
最朴素的做法
ps:朴素无华的解题方法虽然给人一种很 low 的感觉(好吧,实际上是真的 low),但是值的注意的是哪些完美的算法往往是对最朴素的算法进行优化得到的。也就是说,如果掌握了最朴素的方法剩下需要掌握的就是怎么去优化了。
最简单无华的做法莫过于直接 for 循环
for(int i = 1 ; i <= n/i ; i++)
{
if(isPrime(i))
res++;
}
接下来我们开始做优化。多次调用函数会导致算法的时间复杂度增加,那么如何减少调用函数的次数呢?如果我们可以根据已有的数去过滤掉后面的一些数的话,那么我们的复杂度自然会降低。
埃式筛法
int sumPrime(int n)
{
int res = 0;
memset(a, 0, sizeof a);
if(n < 2) return 0;
for(int i = 2 ; i <= n ; i++)
{
if(!a[i])
{
if(isPrime(i))
{
res++;
for(int j = 1 ; j <= n / i; j++)
a[j * i] = 1;
}
}
}
return res;
}
那么这么个筛法有什么缺点吗?答案是显然的,对于两个素数而言他们的公倍数被筛了不只一次。拿2、3来说,他们的公倍数6、12、24…当 i = 2 时只要 j = 3、6、12…前面的数就会被筛出去,当 i = 3 时只要 j = 2、4、6…前面的数同样会被晒出去,也就是说这个做法最大的缺点就是重复。那么,有没有一种方法让一个数只被一个素数筛掉呢?
线性筛法
要解决上面的问题,我们要从数的本身出发,它如何分解才能满足上面的唯一性呢?我们首先回顾一下埃式筛法是如何做的,埃式筛法是通过前面的小的数把后面大的数筛掉。容易实现的就是说前面小的数,因为我们是从1开始循环的,那么怎么保证不重复呢?其实,现在的已知条件变了,变成了对于后面的一个数(c)如何由前面的一个数(b)唯一的表示,即当c/b为何值时(且这个值为我们可以得到的)可以唯一表示后面的数?我们现在有的是前面得到的素数,也就是说c/b为一个c的最小的素数时我们可以唯一的筛去。(这里可能有人会问如果c为素数呢?由于我们的筛法,如果一个数为素数,那么它一定没被筛过)。那么我们如何去确定最小的素数呢??我们记筛选时选取的素数为a,当a|b时就没有必要进行下去了,因为此时b = a*b1,如果继续向下选取的话得到的a’ 会大于a,也就是说最小的素数是a。
int sumPrime(int n)
{
memset(a, 0, sizeof a);
int cnt = 0, p[N];
if(n < 2) return 0;
for(int i = 2 ; i <= n ; i++)
{
if(!a[i])
{
res++;
p[cnt++] = i;
}
for(int j = 0 ; j < cnt ; j++)
{
if(p[j] > n/i)break; //即i*p[j] > n
a[i * p[j]] = 1;
if(i % p[j] == 0)break;
}
}
return res;
}