Haskell递推性能计算中心二项式系数

时间:2015-02-20 04:31:30

标签: haskell recursion

我是Haskell的新手,并学习如何正确使用递归。

以下函数(使用公式计算central binomial coefficients)非常慢;例如,grid (20,20)崩溃了我的笔记本电脑。你能帮我理解为什么吗?

grid::(Integer,Integer)->Integer

grid (1, x)  = 1 + x

grid (x, 1)  = 1 + x

grid (x, y)  = grid ((x-1),y) + grid ((x),(y-1))

4 个答案:

答案 0 :(得分:4)

值得注意的是,您的算法中没有缓存或记忆。 GHC不做魔术,也不会优化那些问题。对于5x5网格,你称为grid 139次,6x6 503,7x7为1847,10x10为97239次。当你达到20x20时,你正在进行如此多的递归调用,这是不可行的。它与做

的概念相同
fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

当你增加n时,你会有一个指数的呼叫,从而减慢你的速度。相反,你可以使用列表和memoization类似于在Fibonacci序列的情况下如何解决这个问题:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

除此之外,您希望它计算二项式系数。至于这种算法的实现,你必须自己解决这个问题;)我可以指出a previous answer of mine解决了生成Pascal三角形的问题。

答案 1 :(得分:1)

执行此函数的原因是爬行是因为它利用了多次递归,例如该函数在每次递归调用时调用自己两次。这意味着在执行此递归函数期间会发生重复计算,并且随着输入大小的增加,计算的时间复杂度呈指数增长。

对于像20这样的较大输入值,这种效果会更明显。

让我们看一下对网格的调用(5,5)。

这扩展如下。

grid(5, 5)

grid(4, 5) + grid(5, 4)

(grid(3, 5) + grid(4, 4)) + (grid(4, 4) + grid(5, 3))

((grid(2, 5) + grid(3, 4)) + (grid(4, 3) + grid(3, 4))) + 
((grid(3, 4) + grid(4, 3)) + (grid(4, 3) + grid(5, 2)))

...and so on

正如您所看到的,即使x和y值较小,事情也会很快失控,网格(3,4)和网格(4,3)会被多次计算。如前所述,使用zipWith的解决方案将更加高效。

答案 2 :(得分:0)

正如其他答案所解释的那样,你的实现问题是递归调用的数量是指数的,即使需要计算的grid (x,y)的不同值的数量只是二次方。

问题的解决方案称为memoization,这基本上意味着缓存之前已计算过的值。我绝对建议你根据@bheklilr推荐的列表编写自己的实现。

现有的库提供了快速解决方案,例如MemoTrie

import Data.MemoTrie

grid :: (Integer, Integer) -> Integer
grid = memo grid'

grid' :: (Integer, Integer) -> Integer
grid' (1, x)  = 1 + x
grid' (x, 1)  = 1 + x
grid' (x, y)  = grid (x - 1, y) + grid (x , y - 1)

请注意,现在grid被定义为 - 它不是多态的,它不需要参数(尽管它的值是一个函数)。对memo的调用会创建一个trie实例,用于缓存所有值,并使用grid'来计算缓存中不存在的值。

答案 3 :(得分:0)

memoization的替代方法是迭代生成行,减少计算次数。

central :: [Integer] -> [Integer]
central x = zipWith (+) x (0:central x)

例如,要生成上一个

中的下一行
> central [1,2,3]
[1,3,6]

或您的网格功能

grid x y = (iterate central [1..]) !! x !! y

和基于零的索引

> grid 2 4
35