我正在使用Ruby中的Project Euler进行问题,并使用Atkin的筛子找到素数,但它比Eratosthenes的筛子运行得慢。有什么问题?
def atkin_sieve(n)
primes = [2,3,5]
sieve = Array.new(n+1, false)
y_upper = n-4 > 0 ? Math.sqrt(n-4).truncate : 1
for x in (1..Math.sqrt(n/4).truncate)
for y in (1..y_upper)
k = 4*x**2 + y**2
sieve[k] = !sieve[k] if k%12 == 1 or k%12 == 5
end
end
y_upper = n-3 > 0 ? Math.sqrt(n-3).truncate : 1
for x in (1..Math.sqrt(n/3).truncate)
for y in (1..y_upper)
k = 3*x**2 + y**2
sieve[k] = !sieve[k] if k%12 == 7
end
end
for x in (1..Math.sqrt(n).truncate)
for y in (1..x)
k = 3*x**2 - y**2
if k < n and k%12 == 11
sieve[k] = !sieve[k]
end
end
end
for j in (5...n)
if sieve[j]
prime = true
for i in (0...primes.length)
if j % (primes[i]**2) == 0
prime = false
break
end
end
primes << j if prime
end
end
primes
end
def erato_sieve(n)
primes = []
for i in (2..n)
if primes.all?{|x| i % x != 0}
primes << i
end
end
primes
end
答案 0 :(得分:5)
作为Wikipedia says,“阿特金的现代筛子更复杂,但在适当优化时更快”(我的重点)。
在第一组循环中节省一些时间的第一个显而易见的地方是当y
大于4*x**2 + y**2
时停止迭代n
。例如,如果n
为1,000,000且x
为450,则应在y
大于435时停止迭代(而不是像目前那样继续执行999)。所以你可以将第一个循环重写为:
for x in (1..Math.sqrt(n/4).truncate)
X = 4 * x ** 2
for y in (1..Math.sqrt(n - X).truncate)
k = X + y ** 2
sieve[k] = !sieve[k] if k%12 == 1 or k%12 == 5
end
end
(这也避免了每次循环时重新计算4*x**2
,但这可能是一个非常小的改进,如果有的话。)
类似的评论当然适用于y
上的其他循环。
第二个可以加快速度的地方是循环y
的策略。循环遍历范围内的y
的所有值,然后检查哪些值导致k
的值具有正确的余数模12。相反,您可以循环遍历{的正确值{仅限{1}},并避免完全测试余数。
如果y
是4模12,那么4*x**2
必须是1或9模12,因此y**2
必须是1,3,5,7或11模12。如果y
为8模12,则4*x**2
必须为5或9模12,因此y**2
必须为3或9模12。最后,如果y
为0模12,然后4*x**2
必须是1或5模12,所以y**2
必须是1,5,7,9或11模12。
我还注意到你对Eratosthenes的筛子通过测试y
以下所有素数的可分性来做无用的工作。一旦您通过小于或等于i
的平方根的所有素数测试可除性,就可以暂停迭代。
答案 1 :(得分:5)
如果您实际上首先正确实施了Eratosthenes的Sieve,那将会有很大帮助。
该筛子的关键特征是每次只能对一个数字进行一次操作。相比之下,你正在为每个素数工作而不是数量。差异很微妙,但性能影响巨大。
以下是您未能实施的实际筛选:
def eratosthenes_primes(n)
primes = []
could_be_prime = (0..n).map{|i| true}
could_be_prime[0] = false
could_be_prime[1] = false
i = 0
while i*i <= n
if could_be_prime[i]
j = i*i
while j <= n
could_be_prime[j] = false
j += i
end
end
i += 1
end
return (2..n).find_all{|i| could_be_prime[i]}
end
将此与您的代码进行比较,以查找高达50,000的所有素数。另请注意,通过特殊套管偶数的逻辑,可以轻松加速2倍。通过这种调整,这个算法对于需要你计算大量素数的每个Project Euler问题都应该足够快。
答案 2 :(得分:1)
@Gareth提到了关于4x ^ 2 + y ^ 2的一些冗余计算。无论是在这里还是在循环中进行计算的其他地方,您都可以利用已经执行的计算并将其简化为简单的添加。
而不是X=4 * x ** 2
,您可以依赖X
已经具有4 * (x-1) ** 2
值的事实。由于4x ^ 2 = 4(x-1)^ 2 + 4(2x - 1),您需要做的就是将8 * x - 4
添加到X
。您可以对k
使用相同的技巧,以及重复计算的其他地方(例如3x ^ 2 + y ^ 2)。