我是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))
答案 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