素数概念
只能被1和本身整除,
需求
指定一个值,取范围内的所有质数
1.普通算法
基本概念:判断这个数,是否存在,能整除大于1小于本身的数(例:6可以整除,2和3,就不是质数)
过滤条件:只判断最小的因数,(例如:6的因数,判断了2,就不需判断3)
所以因数的取值范围最大就是,这个基数的开方。
/**
* 方式1
*/
private void test1(int n) {
int count=0;
StringBuilder sb = new StringBuilder();
sb.append(2);
sb.append(",");
for(int i = 3;i<=n;i++){
boolean isP=true;
for(int j = 2;j <= Math.sqrt(i);j++){
count+=1;
if(i%j==0) {
isP=false;
break;
}
}
if(isP) {
sb.append(i);
sb.append(",");
}
}
System.out.println(count+":"+sb.toString());
}
2.埃式筛法(每找到一个素数将它的倍数筛掉)
基本概念:当2是质数,将4,6,8...都筛掉,剩下的继续
下一个处理3是质数,将3的倍数,6,9,12..都筛掉,剩下继续
下一个5,以此类推。
缺陷:每个相同的倍数被筛选多次(例如:6会被2和3都做一次筛选)
/**
* 方式二
* 埃式筛法
*/
private void test2(int n) {
// 从2开始找
// 每找到一个将倍速筛掉
// 素数集合
int[] primes = new int[n+1];
int cnt=0;// 素数集合下标
// 初期化,下标默认判断
boolean[] st = new boolean[n+1];
for (int i = 0; i<=n; i++) {
st[i] = false;
}
int count=0;
for (int i = 2; i<=n; i++) {
// 判定当前下标值是否已经判定过了,例如6在2判定过,3不再判定
if (!st[i]) {
primes[cnt++]=i;
for (int j=i*i; j<=n; j+=i) {
//这里2的场合筛选了4,6,8,10,12..没意义
//3的场合,会筛12,重复了
st[j] = true;
count+=1;
}
}
}
StringBuilder sb = new StringBuilder();
for (int i=0; i<primes.length;i++) {
sb.append(primes[i]);
sb.append(",");
}
System.out.println(count+":"+sb.toString());
}
3.欧拉筛法
基本概念: 筛掉当前处理的基数的比这个基数小的所有的质数的倍数(例如:基数2,将基数2和质数2的倍数4筛掉,处理基数3就将,3和2的倍数6筛掉,处理4就将,2*4和3*4筛掉,以此类推)
原理1:双数一定会被2筛掉。
原理2:为什么用质数的倍数,因为不是质数,就一定有比本身小的因数,在处理比本身小的因数时就已经筛过了。
/**
* 方式三
* 欧拉筛法(埃式筛法的优化)
*/
private void test3(int n) {
// 素数集合
int[] primes = new int[n+1];
int cnt=0;// 素数集合下标
int count=0;
// 初期化,下标默认判断
boolean[] st = new boolean[n+1];
for (int i = 0; i<=n; i++) {
st[i] = false;
}
for (int i = 2; i<=n; i++) {
if (!st[i]) {
primes[cnt++]=i;
}
for (int j=0;j<cnt && i * primes[j] <= n;j++) {
// 去掉质数的倍数
st[primes[j]*i] = true;
count+=1;
// 除质数能除开,便不是质数
if(i%primes[j]==0)
break;
}
}
StringBuilder sb = new StringBuilder();
for (int i=0; i<primes.length;i++) {
sb.append(primes[i]);
sb.append(",");
}
System.out.println(count+":"+sb.toString());
}
4.自定义函数(自己改了一个函数)
原理1:2以外,双数不做判断(直接用i+=2来循环)节约一半的处理。
原理2:只判断质数的倍数(参照上记3)
原理3:倍数的选择(不是1开始累加)而是从自身的平方开始(例如7的平方,49开始筛查,如果是21在3的倍数筛过滤,所以7不筛了)
原理4:只筛查双数倍(例如:7从49筛,那么单数倍56不筛查(因为一定是偶数),只筛查双数倍63开始)
private void test4(int n) {
// 初期化,下标默认判断
boolean[] st = new boolean[n+1];
// 素数集合
int[] primes = new int[n+1];
int cnt=0;// 素数集合下标
primes[cnt++]=2;
int count=0;
for (int i = 3; i<=n; i+=2) {
if (!st[i]) {
primes[cnt++]=i;
for (int j=i*i; j<=n; j+=(i*2)) {
st[j] = true;
count+=1;
}
}
}
StringBuilder sb = new StringBuilder();
for (int i=0; i<primes.length;i++) {
sb.append(primes[i]);
sb.append(",");
}
System.out.println(count+":"+sb.toString());
}
总结:
注意:本文代码以Int为类型,所以不要传,比int最大值的开方大的数会异常,可以将代码中i*i<n的部分修正为i<sqrt(n))。
测试:以取10000以内素数为例,
方法1:运算了 117,527次,
方法2:运行了 16,981次,
方法3:运行了 8,770次,
方法4:运行了 5,995次,
只限于循环次数,没有计算运行时间,具体效率大家自行摸索。