Python 为什么我的埃拉托什筛这么慢?
我正在解决欧拉项目的一些问题,为了解决一个问题,我不得不生成200万个素数。我对埃拉托什尼筛的实现非常缓慢,但我不知道为什么。有人能解释一下这个实现的主要问题吗。我觉得它很漂亮,然后我发现它非常糟糕:(.我在网上找到了另一个实现,它比我的要快得多Python 为什么我的埃拉托什筛这么慢?,python,performance,algorithm,time-complexity,primes,Python,Performance,Algorithm,Time Complexity,Primes,我正在解决欧拉项目的一些问题,为了解决一个问题,我不得不生成200万个素数。我对埃拉托什尼筛的实现非常缓慢,但我不知道为什么。有人能解释一下这个实现的主要问题吗。我觉得它很漂亮,然后我发现它非常糟糕:(.我在网上找到了另一个实现,它比我的要快得多 def generatePrimes(upperBound): numbers = range(2,upperBound+1) primes = [] while numbers: prime = number
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
运行cProfile显示大部分时间都花在了过滤器上。用列表替换过滤器可以将速度提高大约2倍
numbers = [n for n in numbers if n%prime != 0]
但这并不能真正解决主要问题,即每次迭代都要重新创建数字列表,而且速度很慢
通过用0或类似物替换非素数来标记非素数。埃拉托斯烯的筛子如下所示:
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,因此筛具有渐近时间复杂度O(n*log(log(n)))
,假设算法是常数时间。这非常好
你的函数不会这样做。你的
过滤器
会检查数字
中的每个元素,不管它是否可以被素数
整除。每个元素都会被处理每个素数,直到第一个素数将其整除为止,处理素数p会删除数字
中约1/p的元素。让素数序列为p[0]、p[1]、p[2]等,并且让数的大小序列为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)))
(编辑:我在撰写此答案时没有想到的一个答案是O((n/log(n))^2))这是python2还是python3?首先,它不是Eratosthenes的筛子。它是。我认为从技术上讲,这是一个筛子,但你在每次迭代中都使用过滤函数执行大的分配。大多数筛子都经过优化,只执行一个大数组分配,而你的筛子从len开始执行大小为O(n)的分配(上限)@dustyrockpyle:不,这和Eratosthenes的筛子有很大区别。当外环到达时,SoE对每个数字处理一次,在其素因子分解中对每个素数处理一次;这个函数对每个素数处理一次,直到其第一个素因子。结果是,它对每个数字都处理了很多次平均e次。一件有帮助的事情是,一旦你测试的素数大于sqrt(上界),就打破循环,因为剩下的任何东西都保证是素数。你可以使用yield i
来避免考虑素数的时间复杂度。append(i)
@J.F.Sebastian:据我所知,这不会有什么帮助。收益率
和追加
具有相同的摊销时间复杂度。有人说:“理论上理论和实践没有区别。实践中有。”这都是平方根。如果你分析你的进度,求和;对于这两种情况,计算到上限m
或sqrt(m)
;它们是:对于m<1K…100K:m^1.64…1.80;对于m<1K…100K…1mln:m^1.17…1.27…1.34。如果你在代码中看到n%prime
,这不是对埃拉托什尼的筛选。同意。生成复合物比测试它们更快(引用维基百科)@JamesK是的,因为您仅从其素数因子(低于其sqrt)生成每个组合,但在测试时,每个候选项都由所有素数(低于其sqrt)测试,而不仅仅是由其素数因子测试。