(更新:在这个答案的底部添加了可行的JavaScript代码)
这是埃拉托色尼筛法:
primes = {2, 3, ...} \ ⋃(p ← primes) {p², p²+p, ...}
= {2} ∪ oddPrimes,
oddPrimes = {3, 5, ...} \ ⋃(p ← oddPrimes) {p², p²+2p, ...}
其中 \
表示集合减法,∪
表示集合并,⋃
表示集合的大并。
下面通过一个实例来说明:
{2,3,4,5,6,7,8,9,10,11,12,13,14,15,...}
\ |
{ 4, 6, 8,| 10, 12, 14, 16, 18, ...}
\ .
{ 9, 12, 15, 18, 21, ...}
\
{ 25, 30, 35, ...}
\ { 49, 56, 63, ...}
\ { 121, 132, 143, ...}
\ ........
或者对于奇素数,
{3,5,7,9,11,13,15,17,19,21,23,25,27,29,31, ...}
\ |
{ 9, 15, | 21, 27, 33, ...}
\ .
{ 25, 35, ...}
\ { 49, 63, ...}
\ { 121, 143, ...}
\ ........
问题中提到的代码实现了这种方法。在考虑某个候选者时,sieve
以及循环中的其他变量都处于特定状态。因此,我们只需要直接重新创建这个状态。
假设我们正在考虑 i = 19
作为当前候选者。此时,sieve = { (21, 6) }
,p = 5
。
这意味着对于一个候选者 i
,sieve
包含所有满足 q^2 < i
的质数 q
的倍数,并且 p
是 q
后面的下一个质数。
每个倍数是不小于 i
的最小倍数,并且筛子中没有重复项。那么它就处于一致的状态,并且可以从该点继续执行。
因此,在伪代码中:
primes() = .....
primesFrom(start) =
let
{ primes.next()
; ps = takeWhile( (p => p^2 < start) , primes() )
; p = primes.next_value()
; mults = map( (p => let
{ s = 2*p
; n = (start-p)/s
; r = (start-p)%s
; m = p + (r>0 ? n+1 : n)*s
}
( m, s) )
, ps)
}
for each (m,s) in mults
if m in sieve
increment m by s until m is not in sieve
add (m,s) to sieve
然后执行与之前相同的循环。
根据要求,以下是JS代码:
function *primesFrom(start) {
if (start <= 2) { yield 2; }
if (start <= 3) { yield 3; }
if (start <= 5) { yield 5; }
if (start <= 7) { yield 7; }
const sieve = new Map();
const ps = primesFrom(2);
ps.next();
let p = ps.next().value;
let psqr = p * p;
let c = psqr;
let s = 6;
let m = 9;
while( psqr < start )
{
s = 2 * p;
m = p + s * Math. ceil( (start-p)/s );
while (sieve.has(m)) m += s;
sieve.set(m, s);
p = ps.next().value;
psqr = p * p;
}
if ( start > c) { c = start; }
if ( c%2 === 0 ) { c += 1; }
for ( ; true ; c += 2 )
{
s = sieve.get(c);
if (s !== undefined) {
sieve.delete(c);
} else if (c < psqr) {
yield c;
continue;
} else {
s = 2 * p;
p = ps.next().value;
psqr = p * p;
}
m = c + s;
while (sieve.has(m)) m += s;
sieve.set(m, s);
}
}
正确地在ideone上瞬间找出首10个大于500,000,000的质数:链接。
Success #stdin #stdout 0.03s 17484KB
500000003,500000009,500000041,500000057,500000069,
500000071,500000077,500000089,500000093,500000099
显然,它以惊人的递归深度 5(五次)调用来实现。
重复平方的威力!或者它的反操作,log log
运算:
log2( log2( 500000000 )) == 4.85
i
是什么?s
是什么?p
是什么?为什么代码中没有注释? - Will Ness