素数筛选的原理是先标记某个范围内所有的非素数,然后再遍历统计素数个数,这比一个一个的判别素数快很多。
从最小的素数2开始,一次对2的倍数(至少2倍,这样这个数的倍数一定是非素数)进行标记,2乘2、2乘3、2乘4…
接下来从3开始,3乘2、3乘3、3乘4…
…
标记完后,统计再素数的个数就行了。
使用数组作为标记媒介,代码实现如下,
public static int primeWithArray(int n) {
boolean[] mark = new boolean[n + 1];
int count = 0, i;
for (i = 2; i * i <= n; i++) {
if (!mark[i]) {
count++;
for (int j = 2 * i; j <= n; j += i) {
mark[j] = true;
}
}
}
for (; i <= n; i++) {
if (!mark[i]) {
count++;
}
}
return count;
}
使用BitSet作为标记媒介,代码实现如下,
public static int primeWithBitSet(int n) {
BitSet bitSet = new BitSet(n + 1);
int count = 0, i;
for (i = 2; i * i <= n; i++) {
if (!bitSet.get(i)) {
count++;
for (int j = 2 * i; j <= n; j += i) {
bitSet.set(j);
}
}
}
for (; i <= n; i++) {
if (!bitSet.get(i)) {
count++;
}
}
return count;
}
最后对这两种方式进行测试,
统计2_000_000内的素数个数,分别对数组、BitSet形式运行100次,对比平均耗时时间
import java.util.BitSet;
public class Main {
public static void main(String[] args) {
int n = 2_000_000;
int times = 100;
System.out.println(test(n, Main::primeWithArray, times));
System.out.println(test(n, Main::primeWithBitSet, times));
}
public static double test(int n, Prime prime, int times) {
double sum = 0D;
for (int i = 0; i < times; i++) {
long start = System.currentTimeMillis();
prime.primeSieve(n);
sum += System.currentTimeMillis() - start;
}
return sum / times;
}
public static int primeWithArray(int n) {
boolean[] mark = new boolean[n + 1];
int count = 0, i;
for (i = 2; i * i <= n; i++) {
if (!mark[i]) {
count++;
for (int j = 2 * i; j <= n; j += i) {
mark[j] = true;
}
}
}
for (; i <= n; i++) {
if (!mark[i]) {
count++;
}
}
return count;
}
public static int primeWithBitSet(int n) {
BitSet bitSet = new BitSet(n + 1);
int count = 0, i;
for (i = 2; i * i <= n; i++) {
if (!bitSet.get(i)) {
count++;
for (int j = 2 * i; j <= n; j += i) {
bitSet.set(j);
}
}
}
for (; i <= n; i++) {
if (!bitSet.get(i)) {
count++;
}
}
return count;
}
}
interface Prime {
int primeSieve(int n);
}
测试结果如下,
可以看出使用数组比BitSet还是要高效很多