Java实现快速查找某个范围内的所有素数前言定义法筛选法筛选优化法后记

前言

素数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数。定义非常简单,但是它却难以定量化,研究起来非常复杂,有兴趣的可以买本研究素数的书看看。前几天去B站,看到有关这方面的介绍,给个传送门:素数。

我这里主要是介绍几种查找素数的方法,研究这些算法优化的思路。

定义法

我们一般判断素数都是利用求余的思想,因此查找素数也可以采用这种思想,逐个判断。代码如下:

public static void primes(int n){

if(n < 0){

throw new IllegalArgumentException("N must be a non negative integer.");

}

if(n <= 1){

return new int[0];

}

int primeNums = (n & 1) == 1 ? (n >>> 1) + 1 : n >>> 1;

int[] primeArray = new int[primeNums];

int primeCount = 0;

outer:

for(int i = 2; i <= n; i++){

for(int j = 2, limit = (int) Math.sqrt(i); j <= limit; j++){

if(i % j == 0){

continue outer;

}

}

primeArray[primeCount++] = i;

}

return Arrays.copyOf(primeArray, primeCount );

}

因为一定范围的素数数量难以精确估量,因此我选择最保守的方式,来估计它的数量。n为奇数,数量就定为n/2+1,n为偶数,数量则为n/2。数学上其实有一些估计函数,可以相对精确估计其数量,具体参考百度百科:判断素数

如果大家对判断素数比较了解,应该会对sqrt(n)这个值比较熟悉。该值主要是为了减少重复求余判断,提升效率。网上找了个通俗解释,放个传送门:为什么判断一个数是否为素数时只需开平方根就行了?

这个算法比较简单,效率也相对较低,假设我们输入n=100,我们可能判断了2、4、6、8、10…是否为素数,也判断了3、6、9、12、15…是否为素数,等等。仔细观察,这些数都是素数的倍数。如果我们判断了某个数为素数,再将它的倍数全部设置为合数,那我们判断的数量会大大降低,查找素数的效率也会得到极大提高。就像选择排序相对于冒泡排序,也是减少了大量的swap操作,因此效率也得到了相应提升。

筛选法

前面我们说到定义法找素数的局限性时,提到将素数的倍数设置为合数,提升后面查找素数的效率。这种方法最早被古希腊的埃拉托斯特尼提出。具体代码实现如下:

public static int[] primes(int n){

if(n < 0){

throw new IllegalArgumentException("N must be a non negative integer.");

}

if(n <= 1){

return new int[0];

}

boolean[] primes = new boolean[n + 1]; // 加一实现下标与真实数值对应,boolean默认为false

/* 将下标为奇数的置为true,下标为偶数的默认为false。*/

for(int i = 1; i <= n; i++){

if((i & 1) == 1){

primes[i] = true;

}

}

for(int k = 3, limit = (int) Math.sqrt(n); k <= limit; k += 2){

/*将素数倍数下标处的值全部置false*/

if(isPrime(k)){

for(int i = k * k; i <= n; i += k){

primes[i] = false;

}

}

}

int primeNums = 0;

/*获取精确的素数数量,以免开辟过大的数组造成空间不足的情况。*/

for(boolean isPrime : primes){

if(isPrime){

primeNums++;

}

}

int[] primeArray = new int[primeNums];

primeArray[0] = 2;

int count = 1;

for(int i = 3; i <= n; i++){

if(primes[i]){

primeArray[count++] = i;

}

}

return primeArray;

}

该算法首先初始化n+1(为了让下标和数值对应)长度、boolean类型的prime数组,用于存储是否为素数的信息。然后将奇数下标的值全部设置为true,即认定奇数为伪素数。接着在3:2:sqrt(n)范围内,选定素数,这个判断素数的函数isPrime参见:Java实现素数的判断,然后将该素数的倍数置合,即设置它们为非素数。最后遍历prime数组,将数值为true的下标值认定为素数,并将偶数2也添加入素数数组。

这里我们需要注意的是将素数倍数值置合的代码,如果k是素数,那么我就将k * k : k : n(Matlab写法)的值全部认定为合数,如果你们看过网上其他人的代码,几乎清一色的是k + k : k : n,他们可能认为从k的两倍开始,将所有k的倍数全部取到,但是却忽略了一些东西,打个比方,如果k = 7,我们将14、21、28、35…等设置为合数,这本身并没问题,但是14是2的倍数,在k=2时,我们就已经被置为合数了,21是3的倍数,在k=3的时候也已经被置为合数了,以此类推,我们其实重复操作了很多数据。因此我们选择从k2这个最新倍数开始进行倍数置合操作,它的原理和前面的sqrt(n)一模一样。虽然这个问题不起眼,但我们还是需要注意,写算法时,一定要仔细考虑清楚算法的个个细节。

筛选优化法

前面介绍的筛选法,其实效率已经非常高了,只是有一个问题需要解决,就是它的prime数组开辟的过大,假设n等于Integer.MAX_VALUE,那么该算法直接抛出NegativeArraySizeException,就算比最大值小点,也可能出现OutOfMemoryError,因此我们需要减少该数组的大小。不知大家注意了没,前面筛选法我们在处理前,先将偶数全部置合数。就是说明除了2的偶数全部都是合数,绝不可能是素数,因此我们可以只开辟一半的数组,全部保存奇数。然后再进行后面的筛选操作。代码如下:

public static int[] primes(int n){

if(n < 0){

throw new IllegalArgumentException("N must be a non negative integer.");

}

if(n <= 1){

return new int[0];

}

int len = ((n & 1) == 1) ? (n >> 1) + 1 : n >> 1;

boolean[] p = new boolean[len + 1];

for(int k = 3, limit = (int)Math.sqrt(n); k <= limit; k += 2){

if(!p[(k + 1) >> 1]){

for(int j = (k * k + 1) >> 1 ; j <= len; j += k){

p[j] = true;

}

}

}

int primeNums = 0;

/*获取精确的素数数量,以免开辟过大的数组造成空间不足的情况。*/

for(int i = 1; i <= len; i++){

if(!p[i]){

primeNums++;

}

}

int[] primeArray = new int[primeNums];

primeArray[0] = 2;

int count = 1;

for(int i = 2; i <= len; i++){

if(!p[i]){

primeArray[count++] = i * 2 - 1;

}

}

return Arrays.copyOf(primeArray, count);

}

首先我们的数据全是奇数位,因此实际数据都需要按照2 n - 1进行转换,如下所示

实际数据:1 2 3 4 5 6 7 8 9 10

奇数数据:1 3 5 7 9 11 13 15 17 19

代码先是按奇数数据的3 :2:sqrt(n),取所有奇数,然后判断此时实际数据(k + 1)/ 2的倍数是否已经被置合。如果没有,然后就对其倍数进行置合,在前面筛选法我们知道范围是k2: k : n,此时我们将其转换到实际数据中来,因为奇数数据中的k2转换为实际数据的公式是(2 * p - 1)= k2,因此实际数据的初始点为(k * k + 1)/2,但是我们的步长k却没有转换呢,因为实际数据和奇数数据是线性关系,步长的变换是相同。打个比方,当奇数数据为3时,它的倍数就是9,此时我们按照前面的转换,实际数据的倍数位置就是9对应的5,如果奇数值加个步长3,则转到15,此时我们的实际数据,则按照(2 * n - 1)换算为8,此时实际数据8和5的步长间隔也是3,因此步长不变,截止值从数值n变成n的一半,综上可知奇数数据的范围k2: k : n转换到实际数据时,范围变成了(k2 + 1) / 2 : k : len,这个len就是小于等于n奇数的长。

最后将实际数据中界定为素数的数据全部按照2* i - 1转换为奇数数据,再另外加上2这个特殊的素数,即可。

由此该算法就达到了减少筛选所需的空间的目的,空间效率得到了提升。这一段算法主要参考自Matlab2014a的primes函数,网上好像没有在线的Matlab代码资源,只有安装Matlab软件才能看到。

后记

今天看了一些关于素数的理论知识,还真的挺有意思的,现在想想数学还真是个好东西。最后说一句,很多优秀的算法,Matlab都有相应的实现,并且代码价值非常之高,虽然它的矩阵化操作实现起来效率很低(没有底层支持),但是它的算法思想很值得研究。

java快速查找素数_Java实现快速查找某个范围内的所有素数相关推荐

  1. 信息学奥赛一本通 1411:区间内的真素数 | OpenJudge NOI 1.13 23:区间内的真素数

    [题目链接] ybt 1411:区间内的真素数 OpenJudge NOI 1.13 23:区间内的真素数 [题目考点] 1. 质数 2. 数字拆分 [解题思路] 设函数判断一个数是否是质数 设函数求 ...

  2. java排序方法调用_Java实现顺序查找、二分查找、冒泡排序、方法调用

    上帝没给我成为富二代的机会,但是给了我成为富一代的机会! 主子很萌:敲代码是个快乐的过程代码截图/图1 题目题目/图2 程序源代码 import java.util.Scanner; /** * * ...

  3. c语言列出1~100所有素数_一次找出范围内的所有素数,埃式筛法是什么神仙算法?...

    今天这篇是算法与数据结构专题的第23篇文章,我们继续数论相关的算法,来看看大名鼎鼎的埃式筛法. 我们都知道在数学领域,素数非常重要,有海量的公式和研究关于素数,比如那个非常著名至今没有人解出来的哥德巴 ...

  4. mongodb java id 查询数据_java 用 _id 查找 MongoDB 下的数据

    找网上的资料看了下增删改查,等日后补上. 已经实现了数据的插入,现在想通过 _id属性来查找数据.一开始看到 类似 55b321df715cc162076eb466 这么一长串的内容觉得是string ...

  5. java list初始容量_java中快速创建带初始值的List和Map实例

    java中快速创建带初始值的List和Map实例 初始化一个List和Map对象并为期加入值的写法如下: List sList = new ArrayList(); sList.add("s ...

  6. java窗口程序实例_Java Swing快速构建窗体应用程序

    以前接触java感觉其在桌面开发上,总是不太方便,没有一个好的拖拽界面布局工具,可以快速构建窗体. 最近学习了一下NetBeans IDE 8.1,感觉其窗体设计工具还是很不错的 , 就尝试一下做了一 ...

  7. java 二分查找 排序_java 冒泡排序 二分查找

    下面这个程序是先定义一个整型数组,然后将其中的元素反序赋值,再用冒泡排序进行排序以后用二分查找来查找其中是否有某个数,返回值为-1时表示这个数可能小于这个数组的最小值或大小这个数组的最大值,-2表示这 ...

  8. java数组查找算法_JAVA数组中查找算法中equals和==的问题

    importjava.util.*;publicclassTest1{publicstaticvoidmain(String[]args){ScannerS=newScanner(System.in) ...

  9. java d打字游戏_java实现快速打字游戏

    本文实例为大家分享了java实现打字游戏的具体代码,供大家参考,具体内容如下 import java.util.Random; import java.util.Scanner; public cla ...

  10. java梅森素数_JAVA基础 第三篇:梅森数、梅森素数、伪素数——素数与指数的完美结合与进阶...

    在前面的章节中,我们分别讨论了质数和指数,今天我们不做其他的,仅仅将它们进行整合一下,为什么呢?因为在数学领域,有一种特殊的正整数,形如:2^p - 1,其中指数p为质数,这种数字被称为梅森数,其中的 ...

最新文章

  1. POJ 1556 The Doors(计算几何+最短路)
  2. 无法创建 set/get 参数(参数 ID)
  3. 人工神经网络 说到底,人就是一种机器吗?
  4. php修改html,关于html:用PHP设置innerHTML?
  5. 计算机网络整体框架理解与把握(持续更新)
  6. MFC对话框绘制灰度直方图
  7. 键盘上的反引号怎么打
  8. [翻译:ASP.NET MVC 教程]理解模型、视图和控制器
  9. AOP(基于注解对AspectJ操作)
  10. php反序列化java.long_细数java中Long与Integer比较容易犯的错误总结
  11. CSDN审核机制有点迷惑,决定逐步搬迁到简书
  12. 投标是个技术活,不这样做要么苟且,要么狗带
  13. vue 创建项目使用npm还是yarn
  14. Rhodamine-PEG-Pyrene,罗丹明聚乙二醇芘丁酸,Pyrene-PEG-RB
  15. Cox比例风险回归(Cox ProportionalHazards Model) 到底选用哪种回归分析 r到底选择哪种回归分析 r选择生存分析还是cox分析
  16. 使用Verdi或DVE分析波形的一些小技巧
  17. 【游戏客户端开发】Unity3D 学习笔记2——了解U3D引擎的操作面板和各种工具
  18. 数字化转型顶层设计怎么做?建筑央企数字化转型给出答案
  19. pb文件转java 报:protoc did not exit cleanly. Review output for more information
  20. android倒数计时器,Android倒计时(分钟)

热门文章

  1. springboot + mybatis 学英语网、背单词网站
  2. Hibernate常用配置
  3. phpMyAdmin创建数据库无权限解决方案
  4. python 累加_对Python实现累加函数的方法详解
  5. docker 查看镜像版本_Docker 安装及入门介绍 - 荏苒经十载
  6. 我的世界java版怎么打开聊天栏_我的世界JAVA版才有的隐藏模式只有开发者才知道怎么进入...
  7. git指定版本openwrt源码_关于Github Action自动编译Lean_Openwrt的配置修改问题
  8. PAT之STL:vector、set、map、stack、queue
  9. mysql sql语句执行到一半会怎么样?
  10. 老生常谈exec函数族