【问题标题】:Why is my Sieve of Eratosthenes so slow?为什么我的埃拉托色尼筛法这么慢?
【发布时间】:2015-08-12 20:37:14
【问题描述】:

我正在解决 Project Euler 的一些问题,并且必须生成 200 万个素数才能解决问题。我的 Eratosthenes 筛的实施结果非常缓慢,但我不太清楚为什么。有人可以解释一下这个实现的主要问题。我觉得它很漂亮,然后我发现它非常糟糕:(。我在网上找到了它的另一个实现,它比我的快得多。

def generatePrimes(upperBound):
    numbers = range(2,upperBound+1)
    primes = []

    while numbers:
        prime = numbers[0]
        primes.append(prime)
        numbers = filter((lambda x: x%prime),numbers)
    return primes

编辑:感谢所有答案!对此的结论是过滤器是问题所在,因为它会遍历每个元素(而不仅仅是那些被标记为非素数的元素),并且因为它每次都会创建一个新列表。用旧的 for 循环和一轮过滤重写它,它的工作速度更快。新代码:

def generatePrimes(upperBound):
numbers = range(2,upperBound+1)

for i in xrange(len(numbers)):
    if(numbers[i] != 0):
        for j in xrange(i+numbers[i],len(numbers),numbers[i]):
            numbers[j] = 0

primes = filter(lambda x: x,numbers)
return primes

【问题讨论】:

  • 这是python2还是python3?
  • 一方面,它不是埃拉托色尼的筛子。这是trial division
  • 我认为这在技术上是一个筛子,但您在每次迭代中使用 filter 函数执行大量分配。大多数筛子都经过优化,只执行一次大型数组分配,而您的筛子则从 len(upperBound) 开始执行 O(n) 大小的分配。
  • @dustyrockpyle:不,这与埃拉托色尼筛法有很大不同。当外部循环到达每个数字时,SoE 处理每个数字一次,并在其素数分解中的每个素数处理一次;这个函数为每个素数处理一个数字,直到它的第一个素数因子。事实证明,平均而言,每个数字查看的次数要多得多。
  • 当你测试的素数大于 sqrt(upperBound) 时,有助于打破循环的一件事,因为剩下的任何东西都保证是素数。

标签: python performance algorithm time-complexity primes


【解决方案1】:

埃拉托色尼的筛子是这样的:

def sieve(n):
    primality_flags = [True]*(n+1)
    primality_flags[0] = primality_flags[1] = False
    primes = []
    for i, flag in enumerate(primality_flags):
        if flag:
            primes.append(i)
            for j in xrange(2*i, n+1, i):
                primality_flags[i] = False
    return primes

当外循环到达每个数字时,它处理一次,并且对每个除它的素数处理一次。大约 1/2 的数字可以被 2 整除,大约 1/3 可以被 3 整除,以此类推;渐近地说,每个数字将被处理的平均次数是 1 + 素数倒数之和直到 n。 This sum is about log(log(n)),所以筛子具有渐近时间复杂度O(n*log(log(n))),假设算术是常数时间。这真是太好了。


你的函数不这样做。你的filter 遍历numbers 中的每个元素,无论它是否可以被prime 整除。处理每个素数的每个元素,直到除以它的第一个素数,并且处理素数 p 会删除大约 1/p 的 numbers 元素。设素数序列为p[0]、p[1]、p[2]等,设numbers大小序列为n[0]、n[1]、n[2],等等,我们有以下近似递归:

n[0] = upperBound - 1
n[1] = n[0] * (p[0]-1)/p[0]
n[2] = n[1] * (p[1]-1)/p[1]
...
n[k+1] = n[k] * (p[k]-1)/p[k]

并且您的算法所花费的时间与n 值的总和大致成正比,直到numbers 为空。我没有分析该系列的行为,但计算表明增长比O(n*log(log(n))) 差得多。 (编辑:analysis 在撰写此答案时我没有想到它是 O((n/log(n))^2)。)

【讨论】:

  • 你可以使用yield i来避免考虑primes.append(i)的时间复杂度
  • @J.F.Sebastian:据我所知,这无济于事。 yieldappend 具有相同的摊销时间复杂度。
  • 这是其中一种情况,如果说:“理论上,理论和实践之间没有区别。实际上是有区别的。”
  • 一切都在平方根中。如果你分析你的进展,总结;对于这两种情况,工作到上限msqrt(m)empirical orders of growth 是:对于 m
【解决方案2】:

运行 cProfile 表明大部分时间都花在了过滤器上。将过滤器替换为列表推导式可将处理速度提高约 2 倍。

numbers = [n for n in numbers if n%prime != 0]

但这并不能真正解决主要问题,即每次迭代都在重新创建数字列表,这很慢。更快的实现 http://groups.google.com/group/comp.lang.python/msg/f1f10ced88c68c2d 只是 用 0 或类似符号替换非素数。

【讨论】:

  • 如果你在代码中看到n%prime;它不是埃拉托色尼筛法。
  • 同意。 “生成复合材料比测试它们更快”(引用维基百科)
  • @JamesK 是的,因为您仅从其素数因子(低于其 sqrt)生成每个复合物,但是在测试时,每个候选者都通过所有素数(低于其sqrt),而不仅仅是它的素因数
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多