最简单的筛法代码(算法一)
void prime_filter(bool* is_prime, int n) {
/* 求0~n之间的所有素数
* 要求len(is_prime) > n
*/
memset(is_prime, 1, sizeof(is_prime));
// 0,1是合数
is_prime[0] = is_prime[1] = 0;
// 根据合数必有<=sqrt(n)的素数因子使用筛法
for (int i = 2; i <= sqrt(n); i++) {
if (is_prime[i]) {
// 筛去该素数i所有的倍数j
for (int j = 2; j * i <= n; j++)
is_prime[j * i] = false;
}
}
}
// 根据is_prime遍历即可得素数
基本原理就是合数n必有一个小于等于sqrt(n)的素因子,所以去除所有小于sqrt(n)的素数的倍数即可删除n以内的所有合数。
首先合数n肯定有一个小于等于sqrt(n)的因子(n=x*y),因为合数n必有因子但不可能x,y都大于sqrt(n)必有一个小于等于的,假如是x,再由算术基本定理有x必有素数因子,得证,合数n必有一个小于等于sqrt(n)的素因子。
还有一个需要证明的地方,因为is_prime全初始化为true,如何保证从已知一个素数k开始(因为2必为素数,然后数归即得),到下一个素数g之间的is_prime已被正确设置即合数被筛去。如下:
g和k之间的数m一定比g小的,那么可知m必有素因子,但这个素因子肯定是<=k的,而<=k的素数的倍数全被删去,故得证。
优化重复筛去(算法二)
int prime_faster(int* prime, bool* is_prime, int n) {
/* 求0~n之间的所有素数,优化重复筛去的
* 要求len(prime) > n,len(is_prime) > n
*/
// 素数的数量即当前素数待保存位置索引
int prime_cnt = 0;
memset(is_prime, 1, sizeof(is_prime));
// 0,1是合数
is_prime[0] = is_prime[1] = 0;
// i既是筛去的倍数,也是待测试的素数
for (int i = 2; i <= n; i++) {
if (is_prime[i])
prime[prime_cnt++] = i;
// 筛去当前已找到的每个素数(下标为j)的i倍数
for (int j = 0; j < prime_cnt && i * prime[j] <= n; j++) {
is_prime[i * prime[j]] = false;
// 如果i是prime[j]的倍数
// 那么对于之后的i*prime[j+1],prime[j+2]来说
// 比如i*prime[j+1] = prime[j]*k*prime[j+1],
// 即可在之后更大的i(此时i=k*prime[j+1])对prime[j]的倍数来筛掉
if (i % prime[j] == 0) break;
}
}
return prime_cnt;
}
这里其实有两处优化,先说第一个。
下表是删去当前已知每个素数的倍数的具体循环(没考虑i%prime[j])
i=2 | i=3 | i= 4 | i=5 | i=6 | i=7 |
---|---|---|---|---|---|
2*2 | 3*2 | 4*2 | 5*2 | 6*2 | 7*2 |
3*3 | 4*3 | 5*3 | 6*3 | 7*3 | |
5*5 | 6*5 | 7*5 | |||
7*7 |
对比算法一,比如对于素数7,除了2,3,5倍被筛去,4,6倍从上表中可以看出并未被处理。观察得知比7小的素数倍数可以被处理,没处理全是小于7的合数,而这些合数是可以被比7小的素数所表示的,比如6*7=2*3*7
,所以当i=21时,可被21倍2筛去。并且对于p*k*7
(p*k为合数倍数,p为素数)而言,一定有k*7 >= 7
,即一定会在之后被处理。
待证明:
- 如何证明已知的素数k,到下个素数g之间的is_prime一定被正确筛去,此时和算法一不同在于并不是已知的k及k之前的所有素数倍数都被筛去了。