我在Python 2.7中编写了一个用于创建素数列表的代码。代码是
def primes_list(num):
ans = [2]
for i in range(3, num, 2):
for j in ans:
if i % j == 0:
break
else:
ans.append(i)
else:
return ans
这比Sieve of Eratosthenes更有效吗?我认为内存效率应该更好但我对时间效率有疑问。如何计算时间和内存效率以及如何对效率进行基准测试?
答案 0 :(得分:4)
你正在做的是trial divison - 测试每个候选素数与其下面的每个已知素数。在调用range
时跳过奇数将为你节省一些分数,但这正是基于以下方法的筛选技术:你知道每一个数字都可以被2整除,因此是复合数。筛子只是扩展到:
等等。由于Sieve被认为是最具时效性的算法之一,并且试验部门是 least 之一(维基百科将筛子描述为O(n log log n)
,并根据以下评论,算法可能是O(n^2 / log n)
)),可以合理地假设具有类似Sieve的优化的试验部门没有筛选。
答案 1 :(得分:3)
不,那个试验师在时间复杂性上比eratosthenes的筛子差得多。它的空间复杂度有点更好,但是,因为素数约为n/log(n)
,所以你不会节省大量的内存。也可以使用位向量来完成筛选,这会将常数减少32/64倍(因此实际目的可能会更好)。
显示时间差异的小基准:
>>> timeit.timeit('primes_list(1000)', 'from __main__ import primes_list', number=1000)
0.901777982711792
>>> timeit.timeit('erat(1000)', 'from __main__ import erat', number=1000)
0.2097640037536621
正如您所看到的,即使使用n=1000
eratosthenes也要快4倍。
如果我们将搜索范围增加到10000
:
>>> timeit.timeit('primes_list(10000)', 'from __main__ import primes_list', number=1000)
50.41101098060608
>>> timeit.timeit('erat(10000)', 'from __main__ import erat', number=1000)
2.3083159923553467
现在eratosthenes的速度提高了21倍。正如你所看到的,很明显,eratosthenes 更快。
使用numpy数组很容易将内存减少32或64(取决于您的机器架构)并获得更快的结果:
>>> import numpy as np
>>> def erat2(n):
... ar = np.ones(n, dtype=bool)
... ar[0] = ar[1] = False
... ar[4::2] = False
... for j in xrange(3, n, 2):
... if ar[j]:
... ar[j**2::2*j] = False
... return ar.nonzero()[0]
...
>>> timeit.timeit('erat2(10000)', 'from __main__ import erat2', number=1000)
0.5136890411376953
其他筛子比其他筛子快4倍。
答案 2 :(得分:1)
衡量empirical orders of growth,就是这样! :)
e.g。来自the accepted answer,log_10(50.41/0.9) = 1.75
和log_10(2.31/0.21) = 1.04
的数据,因此您的TD(试验部门)代码的经验增长顺序为 ~ n^1.75
对于Eratosthenes的筛子,范围为1,000 ... 10,000)与 ~ n^1.04
。
后者与 n log log n
一致,前者与 n^2 / log(n)^2
一致,应该如此。
使用g(n) = n^2 / log(n)^2
,我们得到g(10000)/g(1000) = 56.25
,这只比经验值50.41/0.9 = 56.01
低0.4%。但是对于g2(n) = n^2 / log(n)
,我们有g2(10000)/g2(1000) = 75
,这与证据不同。
关于时间复杂度:实际上,大多数复合材料早期失效(是小素数的倍数)。要生成k=n/log(n)
素数,需要O(k^2)
次,在所有前面的素数a.o.t中测试每个素数。只是那些不超过平方根的那些。
复合材料不会增加复杂性(M. ONeill的JFP文章(pg 4)中的精确分析为复合材料提供了与测试sqrt
时质数相同的复杂性 - 每种复合材料都得到保证具有不大于其sqrt
的素因子 - 因此复合材料的复杂性甚至低于此处素数的复杂性。
总的来说,它是O(k^2)
,也就是O(n^2/log(n)^2)
。
只需添加两行,就可以大大提高代码的速度,从O(n^2/log(n)^2)
到O(n^1.5/log(n)^2)
时间复杂度:
for j in ans:
if j*j > i:
ans.append(i); break;
if i % j == 0:
break
# else:
# ans.append(i)
时间复杂度的提高意味着17.8x
的运行时间比率为10,000,超过1,000,而不是之前的56.25x
。这转换为 ~ n^1.25
此范围内经济增长的经验顺序(而不是 ~ n^1.75
)。 10,000次通话的绝对运行时间将比旧的50.41秒更接近2.31秒。
BTW你的原始代码相当于David Turner着名的代码,
primes = sieve [2..]
sieve (x:xs) = x : sieve [y | y <- xs, rem y x /= 0]
和改进的代码,对此:
primes = 2 : sieve [3..] primes
sieve xs (p:ps) | (h,t) <- span (< p*p) xs =
h ++ sieve [y | y <- t, rem y p /= 0] ps
(代码在Haskell中,我认为它足够可读。x:xs
代表一个列表,其中x
是head元素,xs
列表的其余部分。)
答案 3 :(得分:0)
由于Python 2中的range
返回一个列表,因此算法的空间复杂度仍为O(n)
,因此它的空间效率不高(至少不是渐近)。如果你使用了xrange(或Python 3中的范围),那么它会更节省空间,因为你只存储素数 - 而不是所有数字都高达n
。
你的时间效率会比筛子更差。