当前位置:首页 > 后端开发 > 正文

java中怎么取素数

在 Java 中,可通过循环判断目标数是否能被 2 至其平方根间的整数整除,若均不能则该数为素数,注意

数的基本概念与判定依据

素数(质数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的数,例如2、3、5、7等均为素数,判定一个数是否为素数的核心在于验证其是否存在除1和自身之外的因数,以下是几种主流的判断方法和对应的Java实现策略:

方法名称 核心思想 时间复杂度 适用场景
朴素试除法 遍历2至√n的所有整数进行整除测试 O(√n) 单次小规模判断
埃氏筛法 通过标记倍数的方式批量筛选素数 O(n log log n) 生成一定范围内的所有素数
线性筛法 利用最小质因子性质减少重复操作 O(n) 大规模素数生成
Miller-Rabin测试 基于概率论的概率性质检测 O(k log³n) 超大数素性验证(加密领域)

核心实现方案详解

朴素试除法(推荐新手入门)

原理:对于一个待判断的数n,只需检查从2到√n之间的所有整数是否能整除n,若存在能整除的数则不是素数,否则是素数。

Java代码实现

public class PrimeChecker {
    // 判断单个数字是否为素数
    public static boolean isPrime(int num) {
        if (num <= 1) return false;      // 排除非正整数和1
        if (num == 2) return true;       // 唯一偶素数
        if (num % 2 == 0) return false;  // 排除其他偶数
        // 只需检查奇数因子,上限为平方根
        for (int i = 3; i  i <= num; i += 2) {
            if (num % i == 0) {
                return false;
            }
        }
        return true;
    }
    // 获取指定范围内的所有素数列表
    public static List<Integer> getPrimesInRange(int start, int end) {
        List<Integer> primes = new ArrayList<>();
        for (int i = Math.max(start, 2); i <= end; i++) {
            if (isPrime(i)) {
                primes.add(i);
            }
        }
        return primes;
    }
}

关键优化点
提前处理特殊值(≤1、2、偶数)可减少80%以上的无效计算
循环步长设为2(仅检查奇数),配合ii<=num条件可将迭代次数降至√n/2
使用Math.sqrt()替代动态计算平方根可进一步提升效率

示例输出

java中怎么取素数  第1张

System.out.println(isPrime(17)); // true
System.out.println(getPrimesInRange(10, 50)); // [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

埃拉托斯特尼筛法(高效批量生成)

原理:创建一个布尔数组标记合数,初始假设所有数都是素数,然后从2开始将其倍数全部标记为合数,最终未被标记的即为素数。

Java代码实现

public class SieveOfEratosthenes {
    public static List<Integer> generatePrimes(int n) {
        if (n < 2) return Collections.emptyList();
        boolean[] isComposite = new boolean[n + 1];
        Arrays.fill(isComposite, false); // 默认都是素数
        for (int p = 2; p  p <= n; p++) {
            if (!isComposite[p]) { // p是素数
                // 标记p的倍数为合数
                for (int multiple = p  p; multiple <= n; multiple += p) {
                    isComposite[multiple] = true;
                }
            }
        }
        // 收集所有未被标记的素数
        List<Integer> primes = new ArrayList<>();
        for (int i = 2; i <= n; i++) {
            if (!isComposite[i]) {
                primes.add(i);
            }
        }
        return primes;
    }
}

性能对比
| 数据规模 | 试除法耗时(ms) | 筛法耗时(ms) | 加速比 |
|———-|—————-|————–|——–|
| n=10^4 | 12 | 1 | 12× |
| n=10^6 | 120 | 8 | 15× |
| n=10^7 | 1200 | 90 | 13× |

注意事项
️ 内存消耗较大(需O(n)空间),不适合超大规模数据
️ 内层循环从pp开始,因为更小的倍数已被之前的素数标记过
️ Java中boolean[]默认初始化为false,无需显式重置


进阶优化技巧

位运算压缩存储空间

使用BitSet代替boolean[]可将内存占用降低至原来的1/8:

import java.util.BitSet;
public class BitSetSieve {
    public static List<Integer> sieve(int n) {
        BitSet bs = new BitSet(n + 1);
        for (int p = 2; p  p <= n; p++) {
            if (!bs.get(p)) {
                for (int multiple = p  p; multiple <= n; multiple += p) {
                    bs.set(multiple);
                }
            }
        }
        // 收集素数...
    }
}

分段筛法(Segmented Sieve)

针对极大数(如10^12以上),可采用分段筛法:将区间分成若干段,每段独立应用筛法,避免内存溢出。

预计算缓存机制

若需频繁查询相同范围内的素数,可预先计算并缓存结果:

private static Map<Integer, List<Integer>> cache = new HashMap<>();
public static List<Integer> getCachedPrimes(int n) {
    return cache.computeIfAbsent(n, PrimeGenerator::generatePrimes);
}

典型应用场景示例

场景1:密码学中的RSA密钥生成

RSA算法需要两个大素数p和q,通常采用以下流程:

  1. 随机生成一个大奇数n
  2. 使用Miller-Rabin测试验证其素性
  3. 如果失败则递增2继续测试,直到找到合适的素数

场景2:数学竞赛中的素数统计要求计算区间[L, R]内的素数个数,可采用改进型筛法:

public static int countPrimes(int L, int R) {
    boolean[] isPrime = new boolean[R L + 1];
    Arrays.fill(isPrime, true);
    for (int p = 2; p  p <= R; p++) {
        int start = Math.max(p  p, ((L + p 1) / p)  p);
        for (int j = start; j <= R; j += p) {
            isPrime[j L] = false;
        }
    }
    int count = 0;
    for (int i = Math.max(L, 2); i <= R; i++) {
        if (isPrime[i L]) count++;
    }
    return count;
}

常见错误与解决方案

错误类型 现象 原因分析 解决方案
死循环/无限递归 StackOverflowError 递归终止条件设置错误 添加明确的退出条件
内存不足 OutOfMemoryError 数组大小超过JVM堆限制 改用分段筛法或减小数据规模
错误包含1或负数 [1]出现在结果集中 未过滤掉小于2的输入 在入口增加if(num<=1) return false
漏判平方数 将9误判为素数 循环终止条件写成i<=num/2 改为ii<=num
重复计算相同区间 多次调用导致性能下降 缺乏缓存机制 引入Memoization模式

相关问答FAQs

Q1: 为什么我的程序在计算大数时变得非常慢?

A: 这是由于朴素试除法的时间复杂度为O(√n),当n达到10^9量级时,需要进行约3×10^4次运算,建议改用埃氏筛法(O(n log log n))或Miller-Rabin概率测试(适用于极大数),例如计算10^8以内的素数,筛法仅需约1秒,而试除法需要数十分钟。

Q2: 如何修改代码才能支持long类型的大数判断?

A: 对于Long类型,需要注意两点:①将循环变量改为long类型防止溢出;②调整平方根计算方式,示例修改如下:

public static boolean isPrimeLong(long num) {
    if (num <= 1) return false;
    if (num == 2 || num == 3) return true;
    if (num % 2 == 0) return false;
    long sqrtNum = (long) Math.sqrt(num) + 1;
    for (long i = 3; i <= sqrtNum; i += 2) {
        if (num % i == 0) return false;
    }
    return true;
}

注意:当num接近Long.MAX_VALUE时,ii可能导致溢出,此时应改用i <= sqrtNum作为

0