Daniel Wagner 的回答很好地解释了为运行时复杂性推导界限的一般策略。然而,与一般策略的情况一样,它会产生过于保守的界限。
所以,只是为了它,让我们更详细地研究这个例子。
main = print (head (filter isPrime (filter ((==0) . (n `mod`)) [n-1,n-2..])))
where n = 600851475143
(另外:如果n是素数,这会在检查时导致运行时错误n `mod` 0 == 0,因此我将列表更改为,[n, n-1 .. 2]以便算法适用于所有n > 1。)
让我们将表达式拆分为多个部分,以便我们可以更轻松地查看和分析每个部分
main = print answer
where
n = 600851475143
candidates = [n, n-1 .. 2]
divisorsOfN = filter ((== 0) . (n `mod`)) candidates
primeDivisors = filter isPrime divisorsOfN
answer = head primeDivisors
像 Daniel 一样,我假设算术运算、比较等是 O(1) - 虽然不正确,但对于所有远程合理的输入来说,这已经是一个足够好的近似值了。
因此,在列表中,必须生成candidates从n下到上的元素,元素的总成本为. 对于复合,我们有,那就是 Θ(n)。answern - answer + 1O(n - answer + 1)nanswer <= n/2
然后根据需要生成除数列表Θ(n - answer + 1)。
对于 的d(n)除数n,我们可以使用粗略的估计d(n) <= 2√n。
必须检查所有除数>= answer的n素数,这至少是所有除数的一半。由于除数列表是延迟生成的,因此
isPrime :: Integer -> Bool
isPrime p = (divisors p) == [1, p]
是 O(p 的最小素因子),因为一旦> 1找到第一个除数,就确定了相等性检验。对于复合p,最小的质因数是<= √p。
我们< 2√n在最坏的情况下对复杂性进行素数检查 O(√n),并且对复杂性进行一次检查Θ(answer),因此执行的所有素数测试的组合工作是 O(n)。
总而言之,所需的总工作量是O(n),因为每一步的成本是O(n)最坏的。
事实上,这个算法所做的总工作是Θ(n). 如果n是素数,则在 O(1) 中完成所需的除数列表,但素数测试是Θ(n). 如果n是复合的answer <= n/2,并且根据需要生成除数列表是Θ(n)。
如果我们不认为算术运算是 O(1),我们必须乘以对大小为 的数字(即位)的算术运算的复杂度n,O(log n)这取决于所使用的算法,通常会稍微给出一个因子上面log n和下面(log n)^2。