定义
一个数只能被1
和它本身整除,那么这个数就是素数。
问题
求区间之间的素数的个数?如[2, n]
中有多少个素数?
解法
-
初学者的方法:
bool isPrime(int n){ for(int i = 2; i < n; i++) if(n % i == 0) return false; return true; } int countPrimes(int n){ int count = 0; for(int i = 2; i < n; i++) if(isPrime(i)) count ++; return count; }
这样的时间复杂度是 O(n^2),显然不够高效。其实我们只需要稍微修改函数
isPrime
中的for
循环for(int i = 2; i * i < n; i++) if(n % i == 0) return false; eg: 16 = 2 * 8; 16 = 4 * 4; 16 = 8 * 2; //这里就是重复计算的
所以说在
[2, sqrt(n)]
没有找到可整除的因子,在[sqrt(n), n]
区间肯定也没有。这样时间复杂度就一下子缩小到了O(sqrt(n))。 -
高效地解决方法:见下图
我们可以看到,上面的方法其实有很多的重复,或者加做啰嗦,举个栗子:
2是素数,那么:2 × 2 = 4, 2 × 3 = 6, 2 × 4 = 8…肯定均不为素数;
3是素数,那么:3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12…肯定均不为素数。
int countPrimes2(int n){ bool *isPrime = new bool[n + 1]; fill(isPrime, isPrime + 2, true) ; for(int i = 2; i < n; i++){ if(isPrime[i]){ for(int j = i * 2; j < n; j += i) isPrime[j] = false; } } int count = 0; for(int i = 2; i < n; i++) if(isPrime[i]) count ++; delete []isPrime; return count; }
这个代码还有两处值得优化的地方,第一个就是和上面的第一种解法的原因一样,第一个
for
循环的地方:for(int i = 2; i * i< n; i++){ ... }
第二处就是第二个
for
循环的地方,例如当
i = 2
时:4,6,8,10,12,14 被标注为非素数;当
i = 3
时:6,9,12,15,18,21被标注为非素数;当
i = 4
时:6,8,12,16,20,24被标注为非素数;从中可以看出,当i = 4 时,就已经计算了很多重复的计算量,所以我们可以稍微优化一下,如:
for(int i = 2; i < n; i++){ if(isPrime[i]){ for(int j = i * i; j < n; j += i) isPrime[j] = false; } }
这种方法有个名字,叫做sieve of eratosthenes最终的完整代码如下:
int countPrimes2(int n){ bool *isPrime = new bool[n + 1]; fill(isPrime, isPrime + 2, true) ; for(int i = 2; i < n; i++){ if(isPrime[i]){ for(int j = i * 2; j < n; j += i) isPrime[j] = false; } } int count = 0; for(int i = 2; i < n; i++) if(isPrime[i]) count ++; delete []isPrime; return count; }
不过这种方法也有它的缺陷,就是对空间的需求量比较大。