质数定义
只有两个正因数(1和它本身)的自然数即为质数。比1大但不是质数的数称为合数
三种解法
基本方法
基本的方法是通过一个将需要判断的数字num从2–>num-1开始被除,可以整除则不符合其定义。
其所需判断的范围因为因子对称分布,可以缩小到2–>根号num。
public class 素数_基本方法 {
public static void main(String[] args) {
final int N = 100;
for(int i=2;i<=N;i++) {
boolean flag = true;
for(int j=2;j*j<=i;j++) {
if(i%j == 0) {
flag = false;
break;
}
}
if(flag == true)System.out.println(i);
}
}
}
上述解法,需要对每一个数字进行判断。如果我们能够想到在1-100的数字中,除了2以外的倍数一定是合数的想法的话,那么可以考虑埃式筛法。
通过质数的倍数将合数筛掉,不需要再去判断合数。
埃式筛法
用1到sqrt(n)的素数将其倍数标记为合数即可。
public class 素数_埃式筛法 {
public static void main(String[] args) {
// 核心思路是
// 用1到sqrt(n)的素数将其倍数标记为合数即可。
final int NUM = 100;
boolean isprime[] = new boolean[NUM+1];
Arrays.fill(isprime,true); // 假定所有的数为素数
isprime[0] = false;
isprime[1] = false;
for(int i =2;i*i<=NUM;i++) {
if(isprime[i] == true ) {
for(int j=2;i*j<=NUM;j++) {
isprime[i*j] = false;
}
}
}
for(int i =2;i<=NUM;i++) {
if(isprime[i] == true)System.out.println(i);
}
}
}
上述解法,我们通过图分析的话,很容易能够直观的看到判断次数的减少。但是存在一个数字被多个质因子判断的情况,效率低下。如30 会被2、3、5各判断一次。
那么有没有只需要判断一次的解法呢?
于是,最优解法是欧拉筛法
欧拉筛法
public class 素数_欧拉筛法 {
public static void main(String[] args) {
// 其核心思路是
// 每一个合数只被其最小素因子筛一次
// 凡是合数,都有素因子
final int NUM = 100;
int[] prime = new int[NUM]; // 找到的具体的素数
boolean[] isprime = new boolean[NUM+1];
int count = 0;
for(int i = 2;i<=NUM;i++) {
if(isprime[i] == false) prime[count++] = i; // 把当前素数i存放在prime数组第count位置,然后count++;
for(int j = 0;j<count && i*prime[j]<=NUM;j++) {
isprime[i * prime[j]] = true; // prime[j]素数的i倍一定是合数
if(i % prime[j] == 0) break;
// i = prime[j] * k;
// i * prime[j+1] = prime[j] * k * prime[j+1];
}
}
for(int i=0;i<count;i++) {
System.out.println(prime[i]);
}
}
}
欧拉筛法的巧妙之处在每个合数只被其最小的质因子筛一次。以12为例,在for循环中,首先逻辑标识在数字6时被标记为true,不走if语句。往下内层for循环,j为0,对应数组质数为2,将24筛掉。j++时候,我们发现3*12=36,他存在一个2 * 18,只要18的时候将其筛掉即可。在if语句的break判断,注释中写到的内容反映其存在更小 的质因子。
结语:
在后面两种算法,有一句数学结论会更好理解。
所有的合数,都存在质因子。
整体上,欧拉筛法是目前最常用的解法。