“如何衡量效率?”
测量empirical orders of growth,就是这样! :)
例如使用来自 the accepted answer、log_10(50.41/0.9) = 1.75 和 log_10(2.31/0.21) = 1.04 的数据,因此它是 ~ n^1.75 TD(试用部门)代码的经验增长顺序(在 1,000 ... 10,000 范围内)与 ~ n^1.04 相比,Eratosthenes 的筛子。
后者与 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 秒。
顺便说一句,您的原始代码等同于 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 是头部元素,xs 是列表的其余部分)。