import java.util.Scanner;
/*
* 1 ~ sqrt(n) 之类的所有素数倍数去筛选,把素数的倍数标记为合数,剩下的都为素数
* 埃氏算法:将素数的倍数标记为合数,剩下的都为素数
*/
public class 埃氏筛法 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt(); // 给定一个值的范围,求 1 ~ n 之间的素数: 1 ~ Integer.MAX_VALUE -2 (2147483646 - 2)
scan.close();
long startTime = System.currentTimeMillis();
// 假定所有 false 为素数,因为数组下表 0 开始,最高为索引为 n - 1 的数,所以 length + 1,就能得到最高位的 n 下标索引
boolean isPrime[] = new boolean[n + 1]; // 利用索引下标特性作为正整数
// 标记为 true 则是合数,标记为 false 为素数
// 1 和 0 是合数
isPrime[0] = true;
isPrime[1] = true;
//仅为测试数据量时间计数
int countX = 0;
long countY = 0;
int countPrime = 0;
// 假定 i ~ n 为素数,标记他们的倍数为合数即可,剩下没有标记到的都是素数
/*
* 假如给定一个数 n = 36 , <算出它的倍数> 关系如下
* a == 1, 2, 3,4,6
* b == 36,18,12,9,6
* 关系:n = a * b; ———> a = n / b; ———> b = n / a;
* 倍数排序:1,2,3,4,6, 6, 9, 12, 18,36
* 执行次数:1,2,3,4,6,n/6,n/4,n/3,n/2,n/1
* 从关系得知,执行次数 == sqrt(n)根号次,n = sqrt(n)2 的平方
* 编程中 index 可以:1*1=1,2*2=4,3*3=9,4*4=16,5*5=25,index * index < n == 36
* 执行次数 5 次
* 当我标记 2,3,4,5... ~ n 全部数的倍数时,剩下未标记的都是素数
*/
for (int i = 2; i * i < isPrime.length; i++) { // 9 * 9 = 81; 10 * 10 = 100;
if (isPrime[i]) continue; // true 跳过已标记, 编程特性加大性能效率,跳过
countX++;
// j 从 2 开始标记倍数,倍数结果 <= n 范围
for (int j = 2; i * j < isPrime.length; j++) { //倍数小于长度
if (! isPrime[i]) { // 只要是倍数标记为合数,这个判断可以省略
isPrime[i * j] = true;
countY++;
}
}
}
//记录质数个数,只要为 false 都是质数
for (boolean value : isPrime) if (! value) countPrime++;
//打印执行时间与质数个数
System.out.println("执行次数 = " + countX + " + " + countY);
System.out.println("countPrime = " + countPrime);
double useTimems = System.currentTimeMillis() - startTime;
System.out.println("Ending time == " + useTimems / 1000 + "s");
}
}