Performance Haskell函数的运行效率分析
我在Haskell中有以下解决方案:Performance Haskell函数的运行效率分析,performance,haskell,optimization,Performance,Haskell,Optimization,我在Haskell中有以下解决方案: isPrime::Integer->Bool isPrime p=(除数p)=[1,p] 除数::整数->[整数] 除数n=[d | d让我们从顶部开始 divisors :: Integer -> [Integer] divisors n = [d | d <- [1..n], n `mod` d == 0] 在最坏的情况下,p是素数,它强制除数生成其整个输出。与静态已知长度列表的比较是O(1),因此这主要由对除数的调用决定。O(n),O(2
isPrime::Integer->Bool
isPrime p=(除数p)=[1,p]
除数::整数->[整数]
除数n=[d | d让我们从顶部开始
divisors :: Integer -> [Integer]
divisors n = [d | d <- [1..n], n `mod` d == 0]
在最坏的情况下,p
是素数,它强制除数
生成其整个输出。与静态已知长度列表的比较是O(1),因此这主要由对除数的调用决定。O(n),O(2^m)
您的main
函数一次执行一系列操作,因此让我们稍微分解一下子表达式
filter ((==0) . (n `mod`))
它在一个列表上循环,并对每个元素执行O(1)操作。这是O(m),其中m是输入列表的长度
filter isPrime
在列表上循环,对每个元素做O(n)功,其中n是列表中的最大数。如果列表恰好有n个元素长(在您的例子中),这意味着这是O(n*n)功,或者O(2^m*2^m)=O(4^m)功(如上所述,此分析针对的是它生成整个列表的情况)
微小的工作。让我们称之为印刷部分的O(m)
main = print (head (filter isPrime (filter ((==0) . (n `mod`)) [n-1,n-2..])))
考虑到上面的所有子表达式,过滤器isPrime
位显然是主要因素。O(4^m),O(n^2)
现在,还有最后一个需要考虑的细节:在上面的分析中,我一直假设每个函数/子表达式都必须生成其整个输出。正如我们在main
中看到的,这可能不是真的:我们调用head
,这只会强制列表中的一小部分。但是,如果ber本身不是素数,我们可以肯定地知道,我们必须查看至少一半的列表:在n/2
和n
之间肯定不会有除数。因此,我们最好将我们的工作减半——这对渐近成本没有影响。两件事:
任何时候,当你看到一个列表理解(就像你在除数中看到的那样),或者等价地,一些映射
和/或过滤
函数在一个列表上(就像你在main
中看到的那样),把它的复杂性当作Θ(n),就像你在命令式语言中对待for
-循环一样
这可能不是您所期望的那种建议,但我希望它会更有帮助:欧拉计划的部分目的是鼓励您思考各种数学概念的定义,以及可能正确满足这些定义的许多不同算法
好的,第二个建议有点含糊不清……我的意思是,例如,您实现isPrime
的方式实际上是教科书上的定义:
isPrime :: Integer -> Bool
isPrime p = (divisors p) == [1, p]
-- p is prime if its only divisors are 1 and p.
同样地,除数
的实现也很简单:
divisors :: Integer -> [Integer]
divisors n = [d | d <- [1..n], n `mod` d == 0]
-- the divisors of n are the numbers between 1 and n that divide evenly into n.
除数::整数->[整数]
除数n=[d | d很好地解释了推导运行时复杂性边界的一般策略。然而,与一般策略的情况一样,它产生的边界过于保守
所以,让我们更详细地研究一下这个例子
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)——虽然不是真的,但对于所有遥远的合理输入来说,这是一个足够好的近似值
因此,在候选列表中,必须生成从n
到answer
的元素,n-answer+1
元素,总成本为O(n-answer+1)
。对于复合n
,我们找到了答案1
,确定了相等性测试。对于复合p
,最小的素因子是,这里是代码分析/优化的一个很好的参考:。但是,在深入研究之前,我会重新考虑您正在使用的算法。@jtobin通过“运行时效率”我用大O符号在理论层面思考。我可以用命令式语言进行这种类型的分析。我对函数式语言很陌生,所以这种类型的分析对我来说很棘手。“重新思考我的算法”这正是我所想的。所有答案的净问题是,它们没有得到任何这样的暴力代码进行素因子分解的结果,而它可能代表一种“计算方法”,可以得到一个解决方案……它不会在任何合理的时间和功耗内这样做。这不是“代码”问题是“我如何开发更好的算法或“近似算法”(一个通常能更快地给出答案的方法,但可能不会。请参阅素数分解研究。)有趣的是,我今天早上醒来后就在想这件事。如果我在C/C++/Java中编写for循环来测试素数,我会停在sqrt n。现在我只需要弄清楚如何在Haskell中做同样的事情。谢谢你的评论!@Code Guru最简单的方法就是使用takeWhile(\k->k*k@danielfisher我不是在问怎么做=谢谢你回答我的问题。这就是我要求的那种复杂性分析,即使我没有使用那些确切的词语。谢谢你回答我的问题。这是我要求的那种复杂性分析,即使我没有使用那些确切的词语。
divisors :: Integer -> [Integer]
divisors n = [d | d <- [1..n], n `mod` d == 0]
-- the divisors of n are the numbers between 1 and n that divide evenly into n.
main = print (head (filter isPrime (filter ((==0) . (n `mod`)) [n-1,n-2..])))
where n = 600851475143
main = print answer
where
n = 600851475143
candidates = [n, n-1 .. 2]
divisorsOfN = filter ((== 0) . (n `mod`)) candidates
primeDivisors = filter isPrime divisorsOfN
answer = head primeDivisors
isPrime :: Integer -> Bool
isPrime p = (divisors p) == [1, p]