为什么我的Atkin筛的实施比Eratosthenes慢?

syn*_*pse 2 ruby algorithm primes

我正在用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
Run Code Online (Sandbox Code Playgroud)

Gar*_*ees 5

正如维基百科所说,"Atkin的现代筛选更复杂,但在适当优化时更快"(我的重点).

在第一组循环中节省一些时间的第一个显而易见的地方是在大于y时停止迭代.例如,如果是1,000,000并且是450,那么当大于435 时你应该停止迭代(而不是像你现在那样继续到999).所以你可以重写第一个循环:4*x**2 + y**2nnxy

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
Run Code Online (Sandbox Code Playgroud)

(这也避免了4*x**2每次循环重新计算,尽管这可能是一个非常小的改进,如果有的话.)

当然,类似的评论适用于其他循环y.


第二个可以加快速度的地方是循环策略y.循环遍历y范围内的所有值,然后检查哪些值导致k具有正确余数模12的值.相反,您可以只循环右侧的值y,并避免完全测试余数.

如果4*x**2是4模12,那么y**2必须是1或9模12,所以y必须是1,3,5,7或11模12.如果4*x**2是8模12,那么y**2必须是5或9模12,所以y必须是3或9模12.最后,如果4*x**20是模12,那么y**2必须是1或5模12,所以y必须是1,5,7,9或11模12.


我还注意到你对Eratosthenes的筛子通过测试下面所有素数的可分性来做无用的工作i.一旦你通过小于或等于平方根的所有素数测试可除性,你就可以停止迭代i.

  • 关于优化Atve Sieve的实现还有更多问题:请参阅[新编辑的维基百科新伪代码文章](https://en.wikipedia.org/wiki/Sieve_of_Atkin).与使用模60相比,使用模12简化可以使工作增加约23%,并且几乎一半的计算被模数测试拒绝,可以通过添加中间循环来测试模数序列的每个起始编号来纠正,然后通过数组复制序列.最后,对于实际范围,SoA永远不会比最大车轮分解SoE快. (2认同)

bti*_*lly 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
Run Code Online (Sandbox Code Playgroud)

将此与您的代码进行比较,以查找高达50,000的所有素数.另请注意,通过特殊套管偶数的逻辑,可以轻松加速2倍.通过这种调整,这个算法对于需要你计算大量素数的每个Project Euler问题都应该足够快.