总目录
今天使用的方法不再是上篇文章中的遍历法,遍历法非常清晰,非常容易理解,但架不住效率慢,所以介绍一下埃拉托斯特尼的筛选法,方法的本质是把在某一范围内素数的倍数去除掉,剩下的就是新的素数,比如想找出100以内的素数,只需要先找出10以内的素数,然后用10以内的素数与100以内全部的数进行除法运算,如果发现有一个可以整除,就筛掉这个数,经过这样一番操作,没被筛掉的数就是素数了。
发点小感慨,这个埃拉托斯特尼Eratosthenes,是距今二千多年的希腊大师,最让人大跌眼镜的操作就是率先计算出地球的周长(不可思议的是他们那时候就认为地球是球形了),另外开挂的表现就是绘制早期地图。这些希腊先哲创造的辉煌因为战争,知识的传承被中断了,但幸好,阿拉伯人把他们的著作翻译成了自己的文献,而欧洲经历千年的中世纪黑暗后,在文艺复兴期间又把阿拉伯人的文献再翻译过来,这才为西方科学文明的飞跃式发展插上腾飞的翅膀。仔细想一想,如果这期间有一个小小的环节出现纰漏,现代文明根本不复存在,人类将不过是只知道生存的动物而已。所以人类真的是上天极其眷顾的宠儿啊,除了心怀感恩,回报人生的幸运,可能这才是人之所以为人的意义了。
代码思路
- 例如,指定在100的范围内查找全部素数,那么首先要在10以内把全部素数找出来,所以先要用代码生成这样一个数据流
- 将上面的生成的数据流做为从10到100数字范围内,筛选素数的除数,如果发现有一个是可以整除的,就是把这个数去掉,遍历每一个数后,结果就是把素数保留下来了
- 比如:10以内的素数为:2,3,5,7,然后从10开始,与这四个素数相除,发现只要与其中一个素数可以整除,就筛掉
生成指定范围查找素数代码
private IntStream getPrimeStream(int ceilValue) {
return IntStream.rangeClosed(2, ceilValue).filter(v -> {
if (v == 2 || v == 3 || v == 5) return true;
// 这三个素数特别,如果不单独列出来,分分钟告诉你不是素数
if (v % 2 == 0) return false;// 是2的倍数,肯定不是素数
int sqrtValue = Double.valueOf(Math.sqrt(v)).intValue();
return !(IntStream.iterate(3, oddValue -> oddValue + 2).limit(sqrtValue)
.filter(v1 -> v1 <= sqrtValue).anyMatch(v1 -> v % v1 == 0));
});
}
- 上面代码中,生成数列时,使用了rangeClosed, 这样生成的数列可以准确设定范围,使用iterate生成的数列,必须要使用limit方法来限制其长度,否则会永远生成新的数
- 从第6行开始,就是判断素数的遍历法算法了,求出基础素数还是要用遍历法的
- 而求一个数是否是素数,只要从3开始遍历直到这个数的开根号值的整数部分的奇数即可,又可以大大减少运算数据的量
- 为什么是从3开始,因为之前的条件,已经把偶数筛选出去了,因为没有偶数,所以在遍历时,不需要把偶数也遍历,所以生成的数列是从3开始的奇数
IntStream.iterate(3, oddValue -> oddValue + 2)
就生成了这样的奇数数列- 为什么又加了一个filter呢,因为数列中只有奇数,所以数列中就会有大于
sqrtValue
的值,这样的值不必参与运算,就过滤掉
筛选法代码
private void filterEratosthenes(int range) {
int sqrtValue = Double.valueOf(Math.sqrt(range)).intValue();
int[] ints = IntStream.concat(getPrimeStream(sqrtValue), IntStream.rangeClosed(sqrtValue + 1, range)
.filter(v -> !(getPrimeStream(sqrtValue).anyMatch(vp -> v % vp == 0)))).toArray();
System.out.println(ints.length);
}
- 这其中的逻辑比较简单了,就把第一部分生成的数列,逐个与包含每个数的数列进行除法运算,可以整除就筛选掉
- 这其中使用的两数据流的合并,为什么要合并呢?
- 比如,计算100的全部素数,首先通过第一部分代码,把10以内的素数都求出来,那么在使用筛选法时,就不必从头开始,而是只需要从11开始查找到100中间的素数,并将其生成一个新的数列
- 这样一来,就需要把两个数列合并起来,需要注意的是,数列不可复用,所以在代码里调用了两遍第一部分代码
- 两部分代码中没有使用任何循环语句