素数测试的好处是你只除以素数。
private static boolean checkMod( int num) {
for (int i : primes){
if( num % i == 0){
return false;
}
}
return true;
}
坏事是你除以目前找到的所有素数,即所有小于候选的素数。这意味着对于低于 100 万的最大素数 999983,您除以 78497 个素数以发现这个数字是素数。这是很多工作。事实上,当达到 100 万时,在这个算法中花费在素数上的工作占所有工作的 99.9% 左右,在更高的限制中占比更大。而且该算法几乎是二次的,要n
以这种方式找到素数,您需要执行大约
n² / (2*(log n)²)
师。
一个简单的改进是提前停止除法。令n
为合数(即大于 1 且除数不是 1 和的数n
),令d
为 的除数n
。
现在,d
作为整数的除数n
,n/d
也是 : 的n
除数n/(n/d) = d
。所以我们可以很自然地将 的除数分组n
为对,每个除数d
产生对(d, n/d)
。
对于这样的一对,有两种可能:
d = n/d
, 这意味着n = d²
, 或d = √n
.
- 两者不同,然后其中一个比另一个小,比如说
d < n/d
。但这立即转化为d² < n
or d < √n
。
因此,无论哪种方式,每对除数都包含(至少)一个不超过√n
,因此,如果n
是一个合数,则其最小除数(除 1 之外)不超过√n
。
所以我们可以在达到以下条件时停止试用部门√n
:
private static boolean checkMod( int num) {
for (int i : primes){
if (i*i > n){
// We have not found a divisor less than √n, so it's a prime
return true;
}
if( num % i == 0){
return false;
}
}
return true;
}
注意:这取决于按升序迭代的素数列表。如果语言不能保证这一点,则必须使用不同的方法,通过索引迭代 anArrayList
或类似的东西。
在候选的平方根处停止试除法,对于 100 万以下的最大素数 999983,我们现在只需将其除以 1000 以下的 168 个素数。这比以前少了很多工作。在平方根处停止试除法,并且只除以素数,与试除法一样好,并且需要大约
2*n^1.5 / (3*(log n)²)
师,因为n = 1000000
,这是一个大约 750 的因素,还不错,是吗?
但这仍然不是很有效,找到下面所有素数的最有效方法n
是筛子。实施简单的是经典的埃拉托色尼筛法。在 O(n*log log n) 操作中找到下面的素数n
,通过一些增强(预先排除几个小素数的倍数),它的复杂性可以降低到 O(n) 操作。一种具有更好渐近行为的相对较新的筛子是阿特金筛子,它找到了n
在 O(n) 操作中,或者在 O(n/log log n) 操作中消除一些小素数的倍数的增强。阿特金筛子的实现更复杂,因此埃拉托色尼筛子的良好实现可能比阿特金筛子的简单实现表现更好。对于类似优化级别的实现,性能差异很小,除非限制变大(大于 10 10; 并且在实践中,由于更好的内存访问模式,Eratosthenes 的 Sieve 比 Atkin 的 Sieve 具有更好的扩展性并不少见)。因此,我建议从 Eratosthenes 筛开始,并且仅当尽管诚实地优化优化但其性能仍不能令人满意时,才深入研究 Atkin 筛。或者,如果您不想自己实现它,请找到其他人已经认真调整过的良好实现。
我在一个稍微不同的设置的答案中进行了更详细的介绍,问题是找到第 n 个素数。或多或少有效方法的一些实现与该答案相关联,特别是 Eratosthenes 筛的一个或两个可用(尽管没有多少优化)实现。