前言
- 对于寻找质数,即使用sqrt()缩小范围,查找起较多的数据时还是效率很低,无疑就是重复的过多,为此学习了埃氏筛法和欧氏筛法。
埃氏筛 O(nlog(logn))
-
基本思想:从2开始,将每个质数的倍数标记为合数,达到筛选质数的目的。
-
代码
/**
* @param n 查询n以内的质数(不包括n)
* @return primeCount 质数个数
* @author 许钱洲
*/
public static int PrimeCount(int n) {
int primeCount = 0;// 记录质数个数
int[] prime = new int[n];// 存质数
Boolean[] isPrime = new Boolean[n];// true表示i是素数
for (int i = 0; i < n; i++) {
isPrime[i] = true;// 初始化
}
isPrime[0] = isPrime[1] = false;// 0,1不是素数
// 0,1不是素数,直接从2开始
for (int i = 2; i < n; i++) {
// 如果i是质数
if (isPrime[i])
// 记录质数个数并存入prime数组中
prime[primeCount++] = i;
// 找到所有i的倍数,布尔值置为false
for (int j = 2 * i; j < n; j += i) {
isPrime[j] = false;
}
}
return primeCount;
}
缺点
- 对某些数还是会进行重复的筛选,比如12会被26筛也会被34筛,我们考虑进一步的优化。
欧氏筛法 O(n)
- 基本思想:
- 拿12来举例解释,可以更好的理解:12可以拆成2 * 6和3 * 4。
- 当到2时,素数数组prime中存了一个2,那么2 * 2筛掉4,然后prime中存入3,筛掉3 * 2=6和3 * 3=9,然后到4,筛掉4 * 2 =8,此时4 % 2==0所以不再继续下去,不筛掉12,break跳出循环,而12是到6 * 2那里才筛掉的,这样就避免了埃氏筛的重复。
- 而为什么要在i % prime[j] == 0时跳出循环呢,因为4不是12的最小质数,而欧氏筛的核心就是:一个质数 * 一个合数 = 一个更小的质数 * 一个更大的合数。
- 一个质数 * 一个合数 = 一个更小的质数 * 一个更大的合数这一步可以这么证明:
i = p * q(p为i的最小质因子)①
i * prime[j+1]=p * q * prime[j+1]
= p * (q * prime[j+1])
prime[j+1]是一个质数,p也是一个质数,而且由①可知p < prime[j+1],所以应该通p这个位置去找到要筛的数,而不是在prime[j+1]的位置去筛,所以要break。
- 代码
/**
* @param n 查询n以内的质数(包括n)
* @return primeCount 质数个数
* @author 许钱洲
*/
public static int PrimeCount(int n) {
int primeCount = 0;// 记录质数个数
int[] prime = new int[n];// 存质数
Boolean[] isPrime = new Boolean[n + 1];// true表示i是素数
for (int i = 0; i <= n; i++) {
isPrime[i] = true;// 初始化
}
isPrime[0] = isPrime[1] = false;// 0,1不是素数
// 0,1不是素数,直接从2开始
for (int i = 2; i <= n; i++) {
// 如果i是质数
if (isPrime[i])
// 记录质数个数并存入prime数组中
prime[primeCount++] = i;
// 找到所有i的倍数,布尔值置为false
for (int j = 0; j < primeCount && i * prime[j] <= n; j++) {
isPrime[i * prime[j]] = false;
if (i % prime[j] == 0)// 当取得的倍数取余素数为0时,不再继续
break;
}
}
return primeCount;
}
优点
避免了埃氏筛的重复,效率更高