Haskell 为什么这个递归很慢?
我正在努力解决这个问题: 只有1c、5c、10c、25c或50c的硬币,有多少种方式可以得到50美元 这是我的密码:Haskell 为什么这个递归很慢?,haskell,recursion,Haskell,Recursion,我正在努力解决这个问题: 只有1c、5c、10c、25c或50c的硬币,有多少种方式可以得到50美元 这是我的密码: main = print $ coinCombinations [1,5,10,25,50] !! 5000 coinCombinations coins = foldr recurse (1 : repeat 0) coins where recurse a xs = take a xs ++ zipWith (+) (drop a xs) (recurse a xs)
main = print $ coinCombinations [1,5,10,25,50] !! 5000
coinCombinations coins = foldr recurse (1 : repeat 0) coins
where recurse a xs = take a xs ++ zipWith (+) (drop a xs) (recurse a xs)
结果是我的递归
函数很慢,可能是二次时间或更糟。但我不明白为什么,因为它看起来很像斐波那契列表
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
递归的问题是,需要注意不要以指数形式进行分支,也不要有指数形式的内存足迹;此外,编写尾部递归函数通常表达性较差 您可以通过动态编程绕过整个递归开销;它在Haskell中有一个非常出色的实现,使用右折叠:
count :: (Num a, Foldable t) => t Int -> Int -> a
count coins total = foldr go (1: repeat 0) coins !! total
where
go coin acc = out where out = zipWith (+) acc $ replicate coin 0 ++ out
然后:
或如第(1)条所述:
效率较低的替代方案是使用不可变的非严格(装箱)数组:
导入数据数组(listArray,(!)
计数::(数字a,可折叠的t)=>Int->Int->a
计数硬币总数=foldr go init硬币!全部的
哪里
init=listArray(0,总计)$1:重复0
出去
哪里
out=listary(0,总计)$map inc[0..total]
inci=arr!我+如果我<硬币,那么其他0个出来!(i-硬币)
(1) 这个问题已经在stackoverflow的其他地方发布了;看你是对的,这是二次时间。问题是
+------------+
v v
foo a = bar (foo a)
这和
foo a = r
+-------+
v v
where r = bar r
在第一种情况下,两个
foo
函数引用相同的对象,但在第二种情况下,foo
的结果引用相同的对象。因此,在第一种情况下,如果bar
想要引用它已经计算过的fooa
的一部分,它必须重新计算整个过程。只是猜测这与在递归的每一步使用take和drop有很大关系。这些是“O(a)”函数,也许尝试使用splitAt会是一个更好的选择?。另外,请记住,++也是一个“O(a)”操作,因为连接不是使用指针算法完成的,而是通过遍历整个结构完成的。我认为可能是这样,但后来我尝试了一个更简单的recurseone xs=head xs:zipWith(+)(tail xs)(recurseone xs)
而且速度仍然很慢。您是否理解为什么您的代码一开始是正确的?通常,您可以从正确性证明(正确性属性的“终止”部分)推断ressource的使用情况(即复杂度界限)。在调用递归
之间,您不会共享任何工作。如果您说递归一个xs=let l=取一个xs++zipWith(+)(drop a xs)l在l
中,它更像fibs
示例不说这一点会加快速度,因为在foldr
中的recurse
调用之间也没有共享工作。然而,您可以利用惰性来获得一个灵活的动态编程实现。@user3217013在惰性列表中,递归的有效性比良好的基础更重要。我的意思是,让x=1:x在take100x
中终止,因为1:x
是有效的。张贴的fibs
使用相同的技巧。在OP代码中,我猜采用xs++…
部分应该是使其高效的部分(如果a
>0和xs
不为空)。这看起来几乎与我的代码+Derek的编辑完全一样。。。唯一的区别是在前面加零,然后再加,而我使用在前面加一个xs
。所以我想它也会有同样的表现。
import Data.Array (listArray, (!))
count :: (Num a, Foldable t) => t Int -> Int -> a
count coins total = foldr go init coins ! total
where
init = listArray (0, total) $ 1: repeat 0
go coin arr = out
where
out = listArray (0, total) $ map inc [0..total]
inc i = arr ! i + if i < coin then 0 else out ! (i - coin)
+------------+
v v
foo a = bar (foo a)
foo a = r
+-------+
v v
where r = bar r