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)。answer
n - answer + 1
O(n - answer + 1)
n
answer <= 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
。