Java实现高效质数判断算法:从基础到优化的完整指南与常见陷阱解析

引言:质数判断的重要性与挑战
质数(Prime Number)是指大于1的自然数,除了1和它本身以外不再有其他因数的数。质数在密码学、哈希算法、随机数生成等领域有着广泛的应用。在Java编程中,实现高效的质数判断算法是许多开发者必须掌握的技能。本文将从基础到优化,详细解析Java中实现高效质数判断算法的多种方法,并揭示常见的陷阱。
1. 基础质数判断算法:试除法
1.1 算法原理
试除法(Trial Division)是最简单、最直观的质数判断方法。其基本思想是:对于一个给定的数n,从2开始,依次用小于n的所有整数去除n,如果都不能整除,则n是质数。
1.2 Java实现
public class PrimeChecker {
/**
* 基础试除法判断质数
* @param n 待判断的数
* @return 如果是质数返回true,否则返回false
*/
public static boolean isPrimeBasic(int n) {
// 1. 处理小于2的数
if (n < 2) { return false; } // 2. 从2到n-1依次检查是否能整除 for (int i = 2; i < n; i++) { if (n % i == 0) { return false; // 能被整除,不是质数 } } // 3. 所有数都不能整除,是质数 return true; } } 1.3 算法分析 时间复杂度 :O(n),需要执行n-2次除法运算。 空间复杂度 :O(1),只使用了常数级别的额外空间。 优点 :实现简单,易于理解。 缺点 :效率极低,对于大数(如10^9级别)计算时间过长。 1.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrimeBasic(num)); } } 2. 第一次优化:平方根优化 2.1 算法原理 如果n是合数,那么它一定可以写成两个因子的乘积:n = a × b。其中a和b至少有一个小于等于√n。因此,我们只需要检查到√n即可,不需要检查到n-1。 2.2 Java实现 public class PrimeChecker { /** * 平方根优化的质数判断 * @param n 待判断的数 * @return 如果是质数返回true,否则返回false */ public static boolean isPrimeSqrt(int n) { // 1. 处理小于2的数 if (n < 2) { return false; } // 2. 计算平方根 int sqrt = (int) Math.sqrt(n); // 3. 从2到sqrt依次检查是否能整除 for (int i = 2; i <= sqrt; i++) { if (n % i == 0) { return false; } } // 4. 所有数都不能整除,是质数 return true; } } 2.3 算法分析 时间复杂度 :O(√n),相比基础版本有显著提升。 空间复杂度 :O(1)。 优点 :实现简单,效率大幅提升。 缺点 :对于非常大的数(如10^12级别)仍然不够高效。 2.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrimeSqrt(num)); } } 3. 第二次优化:跳过偶数 3.1 算法原理 除了2以外,所有的偶数都不是质数。因此,我们可以先判断n是否为2,如果是则返回true;如果不是,再判断n是否为偶数,如果是则返回false;然后从3开始,每次加2(只检查奇数),直到√n。 3.2 Java实现 public class PrimeChecker { /** * 跳过偶数的质数判断 * @param n 待判断的数 * @return 如果是质数返回true,否则返回false */ public static boolean isPrimeSkipEven(int n) { // 1. 处理小于2的数 if (n < 2) { return false; } // 2. 处理2和偶数 if (n == 2) { return true; } if (n % 2 == 0) { return false; } // 3. 计算平方根 int sqrt = (int) Math.sqrt(n); // 4. 从3开始,每次加2,只检查奇数 for (int i = 3; i <= sqrt; i += 2) { if (n % i == 0) { return false; } } // 5. 所有数都不能整除,是质数 return true; } } 3.3 算法分析 时间复杂度 :O(√n),但常数因子减半。 空间复杂度 :O(1)。 优点 :进一步减少检查次数,效率更高。 缺点 :对于非常大的数仍然不够高效。 3.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrimeSkipEven(num)); } } 4. 第三次优化:6k±1优化 4.1 算法原理 除了2和3以外,所有的质数都可以表示为6k±1的形式(其中k为正整数)。这是因为: 6k:能被2和3整除 6k+2:能被2整除 6k+3:能被3整除 6k+4:能被2整除 6k+5:即6k-1,可能是质数 因此,我们可以只检查6k±1形式的数。 4.2 Java实现 public class PrimeChecker { /** * 6k±1优化的质数判断 * @param n 待判断的数 * @return 如果是质数返回true,否则返回false */ public static boolean isPrime6k(int n) { // 1. 处理小于2的数 if (n < 2) { return false; } // 2. 处理2和3 if (n == 2 || n == 3) { return true; } // 3. 处理能被2或3整除的数 if (n % 2 == 0 || n % 3 == 0) { return false; } // 4. 计算平方根 int sqrt = (int) Math.sqrt(n); // 5. 从5开始,检查6k±1形式的数 for (int i = 5; i <= sqrt; i += 6) { if (n % i == 0 || n % (i + 2) == 0) { return false; } } // 6. 所有数都不能整除,是质数 return true; } } 4.3 算法分析 时间复杂度 :O(√n),但常数因子进一步减小。 空间复杂度 :O(1)。 优点 :效率更高,是单个数质数判断的最优解之一。 缺点 :实现稍复杂。 4.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrime6k(num)); } } 5. 高级优化:埃拉托斯特尼筛法(Sieve of Eratosthenes) 5.1 算法原理 埃拉托斯特尼筛法是一种高效的批量生成质数的方法。其基本思想是: 创建一个布尔数组,标记所有数为质数(true)。 从2开始,将2的倍数标记为非质数(false)。 找到下一个未被标记为非质数的数,重复步骤2。 直到遍历到√n为止。 5.2 Java实现 import java.util.ArrayList; import java.util.List; public class PrimeSieve { /** * 埃拉托斯特尼筛法生成质数列表 * @param limit 上限 * @return 质数列表 */ public static List sieveOfEratosthenes(int limit) {
// 1. 创建布尔数组,初始化为true
boolean[] isPrime = new boolean[limit + 1];
for (int i = 2; i <= limit; i++) { isPrime[i] = true; } // 2. 从2开始筛选 for (int i = 2; i * i <= limit; i++) { if (isPrime[i]) { // 将i的倍数标记为非质数 for (int j = i * i; j <= limit; j += i) { isPrime[j] = false; } } } // 3. 收集所有质数 List primes = new ArrayList<>();
for (int i = 2; i <= limit; i++) { if (isPrime[i]) { primes.add(i); } } return primes; } } 5.3 算法分析 时间复杂度 :O(n log log n),非常高效。 空间复杂度 :O(n),需要一个长度为n+1的数组。 优点 :批量生成质数效率极高。 缺点 :空间复杂度高,不适合判断单个大数。 5.4 使用示例 public static void main(String[] args) { int limit = 100; List primes = sieveOfEratosthenes(limit);
System.out.println(“Primes up to ” + limit + “: ” + primes);
}
6. Miller-Rabin素性测试:大数质数判断
6.1 算法原理
Miller-Rabin素性测试是一种概率性算法,用于判断大数是否为质数。它基于费马小定理和二次探测定理,通过多次随机测试来提高准确性。
6.2 Java实现
import java.util.Random;

public class MillerRabin {
/**
* Miller-Rabin素性测试
* @param n 待测试的数
* @param k 测试次数(准确性随k增加)
* @return 如果是质数返回true,否则返回false
*/
public static boolean isPrime(int n, int k) {
// 1. 处理小于2的数
if (n < 2) { return false; } // 2. 处理2和3 if (n == 2 || n == 2) { return true; } // 3. 处理偶数 if (n % 2 == 0) { return false; } // 4. 将n-1写成d*2^r的形式 int r = 0; int d = n - 1; while (d % 2 == 0) { d /= 2; r++; } // 5. 进行k次测试 Random random = new Random(); for (int i = 0; i < k; i++) { // 随机选择一个a,2 ≤ a ≤ n-2 int a = 2 + random.nextInt(n - 3); // 计算x = a^d mod n long x = modPow(a, d, n); if (x == 1 || x == n - 1) { continue; } boolean composite = true; for (int j = 0; j < r - 1; j++) { x = (x * x) % n; if (x == n - 1) { composite = false; break; } } if (composite) { return false; } } return true; } /** * 模幂运算:计算(base^exponent) % modulus * @param base 底数 * @param exponent 指数 * @param modulus 模数 * @return 计算结果 */ private static long modPow(long base, long exponent, long modulus) { long result = 1; base = base % modulus; while (exponent > 0) {
if (exponent % 2 == 1) {
result = (result * base) % modulus;
}
exponent = exponent >> 1;
base = (base * base) % modulus;
}
return result;
}
}
6.3 算法分析
时间复杂度
:O(k log³n),其中k是测试次数。
空间复杂度
:O(1)。
优点
:可以处理非常大的数(如10^18级别),速度快。
缺点
:是概率性算法,存在极小的误判率(但可以通过增加k来降低)。
6.4 使用示例
public static void main(String[] args) {
long[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101, 1000000007L};

for (long num : testNumbers) {
System.out.println(num + ” is prime? ” + MillerRabin.isPrime(num, 10));
}
}
7. 常见陷阱与注意事项
7.1 整数溢出
问题
:在计算平方根或进行乘法运算时,可能会发生整数溢出。
解决方案
:
使用
long
类型进行中间计算。
使用
Math.sqrt()
计算平方根时,确保n在int范围内。
对于大数,使用BigInteger类。
// 错误示例:n=2^31-1时,i*i可能溢出
for (int i = 2; i * i <= n; i++) { ... } // 正确示例:使用Math.sqrt() int sqrt = (int) Math.sqrt(n); for (int i = 2; i <= sqrt; i++) { ... } // 或者使用long避免溢出 for (long i = 2; i * i <= n; i++) { ... } 7.2 边界条件处理 问题 :容易忽略边界条件,如n=0,1,2,3,4等。 解决方案 :在算法开始处明确处理所有边界情况。 // 错误示例:未处理n=2的情况 if (n < 2) return false; for (int i = 2; i < n; i++) { ... } // 正确示例:明确处理所有边界 if (n < 2) return false; if (n == 2) return true; if (n % 2 == 0) return false; // ... 7.3 浮点数精度问题 问题 :使用 Math.sqrt() 可能引入浮点数精度问题。 解决方案 : 使用 Math.sqrt() 后,将结果强制转换为int。 或者使用整数运算避免浮点数。 // 可能的问题:sqrt(25) = 5.0,但sqrt(26) ≈ 5.099,转换为5 int sqrt = (int) Math.sqrt(n); // 解决方案:确保循环条件正确 for (int i = 2; i <= sqrt; i++) { ... } // 或者 for (int i = 2; i * i <= n; i++) { ... } 7.4 性能陷阱 问题 :在循环中重复计算不变的值,如平方根。 解决方案 :将不变的计算移到循环外部。 // 错误示例:每次循环都计算sqrt for (int i = 2; i <= Math.sqrt(n); i++) { ... } // 正确示例:只计算一次 int sqrt = (int) Math.sqrt(n); for (int i = 2; i <= sqrt; i++) { ... } 7.5 概率性算法的误判 问题 :Miller-Rabin等概率性算法可能误判合数为质数。 解决方案 : 增加测试次数k。 对于关键应用,可以结合确定性算法。 使用已知的确定性Miller-Rabin基底。 // 增加测试次数 MillerRabin.isPrime(n, 20); // k=20 // 对于32位整数,可以使用确定性基底 // 只需测试a=2,7,61即可保证正确性 7.6 内存溢出 问题 :使用筛法时,如果limit很大,布尔数组可能占用过多内存。 解决方案 : 使用位数组(BitSet)减少内存占用。 分段筛法(Segmented Sieve)。 // 使用BitSet减少内存 BitSet isPrime = new BitSet(limit + 1); isPrime.set(2, limit + 1); // 初始化为true for (int i = 2; i * i <= limit; i++) { if (isPrime.get(i)) { for (int j = i * i; j <= limit; j += i) { isPrime.clear(j); } } } 7.7 并发问题 问题 :在多线程环境下,共享的质数判断对象可能出现竞态条件。 解决方案 : 使用局部变量。 使用同步机制。 使用无状态的工具类。 // 无状态的工具类(推荐) public class PrimeUtils { public static boolean isPrime(int n) { ... } } // 多线程安全使用 boolean result = PrimeUtils.isPrime(n); // 无需同步 8. 性能对比与选择建议 8.1 性能对比表 算法 时间复杂度 空间复杂度 适用场景 优点 缺点 基础试除法 O(n) O(1) 教学演示 实现简单 效率极低 平方根优化 O(√n) O(1) 小范围判断 实现简单,效率提升 对大数仍不够快 跳过偶数 O(√n) O(1) 中等范围判断 效率进一步提升 实现稍复杂 6k±1优化 O(√n) O(1) 单个大数判断 效率高,实现简单 实现稍复杂 埃拉托斯特尼筛法 O(n log log n) O(n) 批量生成质数 批量效率极高 空间占用大 Miller-Rabin O(k log³n) O(1) 超大数判断 可处理超大数,速度快 概率性算法 8.2 选择建议 单个小数判断 :使用6k±1优化( isPrime6k )。 批量生成质数 :使用埃拉托斯特尼筛法。 超大数判断 :使用Miller-Rabin素性测试。 教学演示 :使用基础试除法或平方根优化。 中等范围判断 :使用跳过偶数优化。 9. 实际应用示例 9.1 密码学应用:生成大质数 import java.util.Random; public class CryptoPrimeGenerator { /** * 生成指定位数的大质数 * @param bits 位数 * @return 大质数 */ public static long generateLargePrime(int bits) { Random random = new Random(); long candidate; do { // 生成随机奇数 candidate = random.nextLong(); candidate = candidate | 1L; // 确保是奇数 candidate = candidate & ((1L << bits) - 1); // 限制位数 } while (!MillerRabin.isPrime(candidate, 20)); // 使用Miller-Rabin测试 return candidate; } public static void main(String[] args) { // 生成一个64位的大质数 long largePrime = generateLargePrime(64); System.out.println("Generated large prime: " + largePrime); System.out.println("Verified: " + MillerRabin.isPrime(largePrime, 20)); } } 9.2 性能测试:比较不同算法 public class PrimeBenchmark { public static void main(String[] args) { int[] testNumbers = {1000000007, 1000000009, 1000000021, 1000000033}; for (int n : testNumbers) { System.out.println("Testing " + n + ":"); // 测试平方根优化 long start = System.nanoTime(); boolean result1 = PrimeChecker.isPrimeSqrt(n); long time1 = System.nanoTime() - start; System.out.println(" Sqrt: " + result1 + " in " + time1 + " ns"); // 测试6k±1优化 start = System.nanoTime(); boolean result2 = PrimeChecker.isPrime6k(n); long time2 = System.nanoTime() - start; System.out.println(" 6k±1: " + result2 + " in " + time2 + " ns"); // 测试Miller-Rabin start = System.nanoTime(); boolean result3 = MillerRabin.isPrime(n, 10); long time3 = System.nanoTime() - start; System.out.println(" Miller-Rabin: " + result3 + " in " + time3 + " ns"); } } } 10. 总结 本文详细介绍了Java中实现高效质数判断算法的多种方法,从基础的试除法到高级的Miller-Rabin素性测试。每种方法都有其适用场景和优缺点: 基础试除法 :适合教学和理解原理。 平方根优化 :简单有效,适合一般用途。 跳过偶数 :进一步提升效率。 6k±1优化 :单个数判断的最优解之一。 埃拉托斯特尼筛法 :批量生成质数的首选。 Miller-Rabin :处理超大数的利器。 在实际开发中,应根据具体需求选择合适的算法。同时,需要注意整数溢出、边界条件、浮点数精度等常见陷阱,确保算法的正确性和高效性。 通过本文的学习,你应该能够根据不同的应用场景选择和实现最适合的质数判断算法,并避免常见的编程陷阱。 # Java实现高效质数判断算法:从基础到优化的完整指南与常见陷阱解析 引言:质数判断的重要性与挑战 质数(Prime Number)是指大于1的自然数,除了1和它本身以外不再有其他因数的数。质数在密码学、哈希算法、随机数生成等领域有着广泛的应用。在Java编程中,实现高效的质数判断算法是许多开发者必须掌握的技能。本文将从基础到优化,详细解析Java中实现高效质数判断算法的多种方法,并揭示常见的陷阱。 1. 基础质数判断算法:试除法 1.1 算法原理 试除法(Trial Division)是最简单、最直观的质数判断方法。其基本思想是:对于一个给定的数n,从2开始,依次用小于n的所有整数去除n,如果都不能整除,则n是质数。 1.2 Java实现 public class PrimeChecker { /** * 基础试除法判断质数 * @param n 待判断的数 * @return 如果是质数返回true,否则返回false */ public static boolean isPrimeBasic(int n) { // 1. 处理小于2的数 if (n < 2) { return false; } // 2. 从2到n-1依次检查是否能整除 for (int i = 2; i < n; i++) { if (n % i == 0) { return false; // 能被整除,不是质数 } } // 3. 所有数都不能整除,是质数 return true; } } 1.3 算法分析 时间复杂度 :O(n),需要执行n-2次除法运算。 空间复杂度 :O(1),只使用了常数级别的额外空间。 优点 :实现简单,易于理解。 缺点 :效率极低,对于大数(如10^9级别)计算时间过长。 1.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrimeBasic(num)); } } 2. 第一次优化:平方根优化 2.1 算法原理 如果n是合数,那么它一定可以写成两个因子的乘积:n = a × b。其中a和b至少有一个小于等于√n。因此,我们只需要检查到√n即可,不需要检查到n-1。 2.2 Java实现 public class PrimeChecker { /** * 平方根优化的质数判断 * @param n 待判断的数 * @return 如果是质数返回true,否则返回false */ public static boolean isPrimeSqrt(int n) { // 1. 处理小于2的数 if (n < 2) { return false; } // 2. 计算平方根 int sqrt = (int) Math.sqrt(n); // 3. 从2到sqrt依次检查是否能整除 for (int i = 2; i <= sqrt; i++) { if (n % i == 0) { return false; } } // 4. 所有数都不能整除,是质数 return true; } } 2.3 算法分析 时间复杂度 :O(√n),相比基础版本有显著提升。 空间复杂度 :O(1)。 优点 :实现简单,效率大幅提升。 缺点 :对于非常大的数(如10^12级别)仍然不够高效。 2.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrimeSqrt(num)); } } 3. 第二次优化:跳过偶数 3.1 算法原理 除了2以外,所有的偶数都不是质数。因此,我们可以先判断n是否为2,如果是则返回true;如果不是,再判断n是否为偶数,如果是则返回false;然后从3开始,每次加2(只检查奇数),直到√n。 3.2 Java实现 public class PrimeChecker { /** * 跳过偶数的质数判断 * @param n 待判断的数 * @return 如果是质数返回true,否则返回false */ public static boolean isPrimeSkipEven(int n) { // 1. 处理小于2的数 if (n < 2) { return false; } // 2. 处理2和偶数 if (n == 2) { return true; } if (n % 2 == 0) { return false; } // 3. 计算平方根 int sqrt = (int) Math.sqrt(n); // 4. 从3开始,每次加2,只检查奇数 for (int i = 3; i <= sqrt; i += 2) { if (n % i == 0) { return false; } } // 5. 所有数都不能整除,是质数 return true; } } 3.3 算法分析 时间复杂度 :O(√n),但常数因子减半。 空间复杂度 :O(1)。 优点 :进一步减少检查次数,效率更高。 缺点 :对于非常大的数仍然不够高效。 3.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrimeSkipEven(num)); } } 4. 第三次优化:6k±1优化 4.1 算法原理 除了2和3以外,所有的质数都可以表示为6k±1的形式(其中k为正整数)。这是因为: 6k:能被2和3整除 6k+2:能被2整除 6k+3:能被3整除 6k+4:能被2整除 6k+5:即6k-1,可能是质数 因此,我们可以只检查6k±1形式的数。 4.2 Java实现 public class PrimeChecker { /** * 6k±1优化的质数判断 * @param n 待判断的数 * @return 如果是质数返回true,否则返回false */ public static boolean isPrime6k(int n) { // 1. 处理小于2的数 if (n < 2) { return false; } // 2. 处理2和3 if (n == 2 || n == 3) { return true; } // 3. 处理能被2或3整除的数 if (n % 2 == 0 || n % 3 == 0) { return false; } // 4. 计算平方根 int sqrt = (int) Math.sqrt(n); // 5. 从5开始,检查6k±1形式的数 for (int i = 5; i <= sqrt; i += 6) { if (n % i == 0 || n % (i + 2) == 0) { return false; } } // 6. 所有数都不能整除,是质数 return true; } } 4.3 算法分析 时间复杂度 :O(√n),但常数因子进一步减小。 空间复杂度 :O(1)。 优点 :效率更高,是单个数质数判断的最优解之一。 缺点 :实现稍复杂。 4.4 使用示例 public static void main(String[] args) { int[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101}; for (int num : testNumbers) { System.out.println(num + " is prime? " + isPrime6k(num)); } } 5. 高级优化:埃拉托斯特尼筛法(Sieve of Eratosthenes) 5.1 算法原理 埃拉托斯特尼筛法是一种高效的批量生成质数的方法。其基本思想是: 创建一个布尔数组,标记所有数为质数(true)。 从2开始,将2的倍数标记为非质数(false)。 找到下一个未被标记为非质数的数,重复步骤2。 直到遍历到√n为止。 5.2 Java实现 import java.util.ArrayList; import java.util.List; public class PrimeSieve { /** * 埃拉托斯特尼筛法生成质数列表 * @param limit 上限 * @return 质数列表 */ public static List sieveOfEratosthenes(int limit) {
// 1. 创建布尔数组,初始化为true
boolean[] isPrime = new boolean[limit + 1];
for (int i = 2; i <= limit; i++) { isPrime[i] = true; } // 2. 从2开始筛选 for (int i = 2; i * i <= limit; i++) { if (isPrime[i]) { // 将i的倍数标记为非质数 for (int j = i * i; j <= limit; j += i) { isPrime[j] = false; } } } // 3. 收集所有质数 List primes = new ArrayList<>();
for (int i = 2; i <= limit; i++) { if (isPrime[i]) { primes.add(i); } } return primes; } } 5.3 算法分析 时间复杂度 :O(n log log n),非常高效。 空间复杂度 :O(n),需要一个长度为n+1的数组。 优点 :批量生成质数效率极高。 缺点 :空间复杂度高,不适合判断单个大数。 5.4 使用示例 public static void main(String[] args) { int limit = 100; List primes = sieveOfEratosthenes(limit);
System.out.println(“Primes up to ” + limit + “: ” + primes);
}
6. Miller-Rabin素性测试:大数质数判断
6.1 算法原理
Miller-Rabin素性测试是一种概率性算法,用于判断大数是否为质数。它基于费马小定理和二次探测定理,通过多次随机测试来提高准确性。
6.2 Java实现
import java.util.Random;

public class MillerRabin {
/**
* Miller-Rabin素性测试
* @param n 待测试的数
* @param k 测试次数(准确性随k增加)
* @return 如果是质数返回true,否则返回false
*/
public static boolean isPrime(int n, int k) {
// 1. 处理小于2的数
if (n < 2) { return false; } // 2. 处理2和3 if (n == 2 || n == 2) { return true; } // 3. 处理偶数 if (n % 2 == 0) { return false; } // 4. 将n-1写成d*2^r的形式 int r = 0; int d = n - 1; while (d % 2 == 0) { d /= 2; r++; } // 5. 进行k次测试 Random random = new Random(); for (int i = 0; i < k; i++) { // 随机选择一个a,2 ≤ a ≤ n-2 int a = 2 + random.nextInt(n - 3); // 计算x = a^d mod n long x = modPow(a, d, n); if (x == 1 || x == n - 1) { continue; } boolean composite = true; for (int j = 0; j < r - 1; j++) { x = (x * x) % n; if (x == n - 1) { composite = false; break; } } if (composite) { return false; } } return true; } /** * 模幂运算:计算(base^exponent) % modulus * @param base 底数 * @param exponent 指数 * @param modulus 模数 * @return 计算结果 */ private static long modPow(long base, long exponent, long modulus) { long result = 1; base = base % modulus; while (exponent > 0) {
if (exponent % 2 == 1) {
result = (result * base) % modulus;
}
exponent = exponent >> 1;
base = (base * base) % modulus;
}
return result;
}
}
6.3 算法分析
时间复杂度
:O(k log³n),其中k是测试次数。
空间复杂度
:O(1)。
优点
:可以处理非常大的数(如10^18级别),速度快。
缺点
:是概率性算法,存在极小的误判率(但可以通过增加k来降低)。
6.4 使用示例
public static void main(String[] args) {
long[] testNumbers = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 19, 23, 97, 101, 1000000007L};

for (long num : testNumbers) {
System.out.println(num + ” is prime? ” + MillerRabin.isPrime(num, 10));
}
}
7. 常见陷阱与注意事项
7.1 整数溢出
问题
:在计算平方根或进行乘法运算时,可能会发生整数溢出。
解决方案
:
使用
long
类型进行中间计算。
使用
Math.sqrt()
计算平方根时,确保n在int范围内。
对于大数,使用BigInteger类。
// 错误示例:n=2^31-1时,i*i可能溢出
for (int i = 2; i * i <= n; i++) { ... } // 正确示例:使用Math.sqrt() int sqrt = (int) Math.sqrt(n); for (int i = 2; i <= sqrt; i++) { ... } // 或者使用long避免溢出 for (long i = 2; i * i <= n; i++) { ... } 7.2 边界条件处理 问题 :容易忽略边界条件,如n=0,1,2,3,4等。 解决方案 :在算法开始处明确处理所有边界情况。 // 错误示例:未处理n=2的情况 if (n < 2) return false; for (int i = 2; i < n; i++) { ... } // 正确示例:明确处理所有边界 if (n < 2) return false; if (n == 2) return true; if (n % 2 == 0) return false; // ... 7.3 浮点数精度问题 问题 :使用 Math.sqrt() 可能引入浮点数精度问题。 解决方案 : 使用 Math.sqrt() 后,将结果强制转换为int。 或者使用整数运算避免浮点数。 // 可能的问题:sqrt(25) = 5.0,但sqrt(26) ≈ 5.099,转换为5 int sqrt = (int) Math.sqrt(n); // 解决方案:确保循环条件正确 for (int i = 2; i <= sqrt; i++) { ... } // 或者 for (int i = 2; i * i <= n; i++) { ... } 7.4 性能陷阱 问题 :在循环中重复计算不变的值,如平方根。 解决方案 :将不变的计算移到循环外部。 // 错误示例:每次循环都计算sqrt for (int i = 2; i <= Math.sqrt(n); i++) { ... } // 正确示例:只计算一次 int sqrt = (int) Math.sqrt(n); for (int i = 2; i <= sqrt; i++) { ... } 7.5 概率性算法的误判 问题 :Miller-Rabin等概率性算法可能误判合数为质数。 解决方案 : 增加测试次数k。 对于关键应用,可以结合确定性算法。 使用已知的确定性Miller-Rabin基底。 // 增加测试次数 MillerRabin.isPrime(n, 20); // k=20 // 对于32位整数,可以使用确定性基底 // 只需测试a=2,7,61即可保证正确性 7.6 内存溢出 问题 :使用筛法时,如果limit很大,布尔数组可能占用过多内存。 解决方案 : 使用位数组(BitSet)减少内存占用。 分段筛法(Segmented Sieve)。 // 使用BitSet减少内存 BitSet isPrime = new BitSet(limit + 1); isPrime.set(2, limit + 1); // 初始化为true for (int i = 2; i * i <= limit; i++) { if (isPrime.get(i)) { for (int j = i * i; j <= limit; j += i) { isPrime.clear(j); } } } 7.7 并发问题 问题 :在多线程环境下,共享的质数判断对象可能出现竞态条件。 解决方案 : 使用局部变量。 使用同步机制。 使用无状态的工具类。 // 无状态的工具类(推荐) public class PrimeUtils { public static boolean isPrime(int n) { ... } } // 多线程安全使用 boolean result = PrimeUtils.isPrime(n); // 无需同步 8. 性能对比与选择建议 8.1 性能对比表 算法 时间复杂度 空间复杂度 适用场景 优点 缺点 基础试除法 O(n) O(1) 教学演示 实现简单 效率极低 平方根优化 O(√n) O(1) 小范围判断 实现简单,效率提升 对大数仍不够快 跳过偶数 O(√n) O(1) 中等范围判断 效率进一步提升 实现稍复杂 6k±1优化 O(√n) O(1) 单个大数判断 效率高,实现简单 实现稍复杂 埃拉托斯特尼筛法 O(n log log n) O(n) 批量生成质数 批量效率极高 空间占用大 Miller-Rabin O(k log³n) O(1) 超大数判断 可处理超大数,速度快 概率性算法 8.2 选择建议 单个小数判断 :使用6k±1优化( isPrime6k )。 批量生成质数 :使用埃拉托斯特尼筛法。 超大数判断 :使用Miller-Rabin素性测试。 教学演示 :使用基础试除法或平方根优化。 中等范围判断 :使用跳过偶数优化。 9. 实际应用示例 9.1 密码学应用:生成大质数 import java.util.Random; public class CryptoPrimeGenerator { /** * 生成指定位数的大质数 * @param bits 位数 * @return 大质数 */ public static long generateLargePrime(int bits) { Random random = new Random(); long candidate; do { // 生成随机奇数 candidate = random.nextLong(); candidate = candidate | 1L; // 确保是奇数 candidate = candidate & ((1L << bits) - 1); // 限制位数 } while (!MillerRabin.isPrime(candidate, 20)); // 使用Miller-Rabin测试 return candidate; } public static void main(String[] args) { // 生成一个64位的大质数 long largePrime = generateLargePrime(64); System.out.println("Generated large prime: " + largePrime); System.out.println("Verified: " + MillerRabin.isPrime(largePrime, 20)); } } 9.2 性能测试:比较不同算法 public class PrimeBenchmark { public static void main(String[] args) { int[] testNumbers = {1000000007, 1000000009, 1000000021, 1000000033}; for (int n : testNumbers) { System.out.println("Testing " + n + ":"); // 测试平方根优化 long start = System.nanoTime(); boolean result1 = PrimeChecker.isPrimeSqrt(n); long time1 = System.nanoTime() - start; System.out.println(" Sqrt: " + result1 + " in " + time1 + " ns"); // 测试6k±1优化 start = System.nanoTime(); boolean result2 = PrimeChecker.isPrime6k(n); long time2 = System.nanoTime() - start; System.out.println(" 6k±1: " + result2 + " in " + time2 + " ns"); // 测试Miller-Rabin start = System.nanoTime(); boolean result3 = MillerRabin.isPrime(n, 10); long time3 = System.nanoTime() - start; System.out.println(" Miller-Rabin: " + result3 + " in " + time3 + " ns"); } } } 10. 总结 本文详细介绍了Java中实现高效质数判断算法的多种方法,从基础的试除法到高级的Miller-Rabin素性测试。每种方法都有其适用场景和优缺点: 基础试除法 :适合教学和理解原理。 平方根优化 :简单有效,适合一般用途。 跳过偶数 :进一步提升效率。 6k±1优化 :单个数判断的最优解之一。 埃拉托斯特尼筛法 :批量生成质数的首选。 Miller-Rabin :处理超大数的利器。 在实际开发中,应根据具体需求选择合适的算法。同时,需要注意整数溢出、边界条件、浮点数精度等常见陷阱,确保算法的正确性和高效性。 通过本文的学习,你应该能够根据不同的应用场景选择和实现最适合的质数判断算法,并避免常见的编程陷阱。