目录
一、什么是素数?
素数是只能被1和自身整除的自然数(如2,3,5,7)。判断素数的核心问题在于如何高效检测因数。
二、暴力遍历法:最朴素的解决方案
算法思想
检查区间内每个数n是否能被2到n-1之间的任何数整除。
boolean isPrime(int n) { if (n <= 1) return false; for (int i = 2; i < n; i++) { if (n % i == 0) return false; } return true; }
时间复杂度:O(n²) 问题:当n=10⁶时,需要循环近万亿次!
三、n/2优化:砍掉一半的无效劳动
关键发现
如果n能被大于n/2的数整除,对应的另一个因数必然小于2,但自然数最小因数是2。因此只需检查到n/2。
boolean isPrimeOptimizedHalf(int n) { if (n <= 1) return false; for (int i = 2; i <= n/2; i++) { // 终止条件改为n/2 if (n % i == 0) return false; } return true; }
时间复杂度:O(n²/2) → 仍为O(n²)量级 优势:减少约50%循环次数 痛点:当n=10⁶时仍需循环50万次
四、√n优化:数学思维的降维打击
数学原理
若n有因数a和b(n=a×b),必有一个因数≤√n。例如:
-
36=6×6 → 边界情况
-
24=3×8 → 3≤√24≈4.9
-
17=1×17 → 没有其他因数
boolean isPrimeSqrt(int n) { if (n <= 1) return false; int limit = (int)Math.sqrt(n); // 计算平方根 for (int i = 2; i <= limit; i++) { if (n % i == 0) return false; } return true; }
时间复杂度:O(n√n) 性能提升:当n=10⁶时,仅需循环1000次! 通俗理解:就像找钥匙时,确定不在客厅就无需搜查整个房子
五、埃拉托斯特尼筛法:空间换时间的典范
算法思想
-
创建布尔数组标记数字状态
-
从2开始,标记所有倍数
-
未被标记的即为素数
void sieveOfEratosthenes(int max) { boolean[] isPrime = new boolean[max+1]; Arrays.fill(isPrime, true); isPrime[0] = isPrime[1] = false; for (int i = 2; i*i <= max; i++) { if (isPrime[i]) { for (int j = i*i; j <= max; j += i) { isPrime[j] = false; } } } // 输出结果 for (int i = 2; i <= max; i++) { if (isPrime[i]) System.out.print(i + " "); } }
时间复杂度:O(n log log n) → 接近线性 优势:找100万以内素数仅需约1毫秒 空间消耗:需要O(n)内存空间
六、算法对比实验
测试环境:Intel i7-12700H,查找10⁶以内素数
算法 | 执行时间 | 循环次数 |
---|---|---|
暴力法 | 超时 | ≈5×10¹¹ |
n/2优化 | 12.3秒 | ≈5×10¹⁰ |
√n优化 | 0.8秒 | ≈7.8×10⁶ |
埃拉托斯特尼筛法 | 0.002秒 | ≈1.8×10⁶次标记 |
七、如何选择算法?
-
单次判断:优先√n优化
-
区间查找:埃氏筛法完胜
-
内存敏感场景:考虑分段筛法
思考题:如果要找10¹²级别的素数,哪种算法更合适?(提示:考虑米勒-拉宾素性测试)
通过这趟算法演进之旅,我们见证了从暴力穷举到数学优化,再到空间换时间的思维跃迁。编程之美,正在于这种不断突破极限的探索过程!