为什么foldl有时比foldr慢? 我有一个列表“a”,如
a = [[1],[2],[3]]
并希望使用fold
将其更改为列表foldr (++) [] a
工作正常(时间复杂度为O(n))。但是,如果我使用foldl,它非常慢(时间复杂度为O(n ^ 2))。
foldl (++) [] a
如果foldl只是从左侧折叠输入列表,
(([] ++ [1]) ++ [2]) ++ [3]
和foldr来自右侧,
[1] ++ ([2] ++ ([3] ++ []))
在两种情况下,计算次数(++)应该相同。为什么折叠这么慢?根据时间复杂度,foldl看起来好像扫描输入列表的次数是foldr的两倍。我用以下计算机来计算时间
length $ fold (++) [] $ map (:[]) [1..N] (fold is either foldl or foldr)
答案 0 :(得分:11)
这是因为++
的工作方式。请注意,is是通过第一个参数的归纳来定义的。
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
递归调用的数量仅取决于length xs
。
因此,在xs ++ ys
中,使用小xs
和大ys
比使用其他方式更方便。
请注意,右侧的折叠实现了这一点:
[1] ++ ([2] ++ ([3] ++ ...
++
左侧只有短(O(1) - 长度)列表,折现成本为O(n)。
相反,在左边折叠很糟糕:
((([] ++ [1]) ++ [2]) ++ [3]) ++ ...
左参数变得越来越大。因此,我们在这里得到O(n ^ 2)复杂度。
考虑到输出列表的需求,可以更准确地说明这个参数。可以观察到foldr
在"流媒体"中产生了它的结果。时尚,要求例如第一个输出列表单元只强制输入的一小部分 - 只需要执行第一个++
,并且实际上只需要它的第一个递归步骤!相反,即使只需要foldl
结果中的第一项,也会强制所有 ++
次调用,这使得它非常昂贵(即使每次调用只需要一个递归步骤,也有O(n)致电)。