递归的替代使用如何影响Haskell列表反转的效率?

时间:2014-10-20 15:22:52

标签: haskell recursion

经过漫长的休息后,我正在给自己一个关于Haskell的复习。我正在研究99 Haskell Problem。我为#5编写了一个解决方案,然后看一下alternative solutions来扩展我的知识。以下是#5的解决方案页面的摘录:

  

撤消列表。

     

前言中的标准定义简明扼要,但不是   很可读。另一种定义反向的方法是:

reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
     

然而,这个定义比Prelude中的定义更浪费   在累积结果时反复重新计算结果。下列   变异避免了这种情况,因而在计算上更接近Prelude   版本

reverse :: [a] -> [a]
reverse list = reverse' list []
  where
    reverse' [] reversed     = reversed
    reverse' (x:xs) reversed = reverse' xs (x:reversed)

有人可以解释为什么第二种解决方案比第一种解决方案更有效吗?我可以看到两种解决方案都使用递归,但我还不能理解差异如何影响效率,是否与Haskell遍历列表有关?

1 个答案:

答案 0 :(得分:3)

第一个解决方案使用递归,但它不是尾递归,递归调用是函数执行的最后一个。尾递归比非尾递归更有效,因为函数不需要在函数调用后继续执行,因此它的堆栈帧等可以重用。

第一个解决方案首先执行递归调用,然后在结果和单个元素列表上调用连接运算符。第二个解决方案首先将元素添加到列表中,然后再次调用自身,而无需进一步执行任何操作。

除此之外,++需要第一个参数长度的线性时间,因为它需要复制列表的每个节点,以便它可以将最后一个节点更改为指向第一个节点的第一个节点。右侧列表。这是因为第一个变量在内部完成了它的实际工作(首先它以递归方式调用自身,然后在返回之后,它执行实际工作,这意味着内部调用将首先完成它们的工作),或者换句话说,它处理个别列表元素从右到左。因此,当它遇到一个新元素(在原始列表的左侧更远)时,需要将它添加到列表的右侧。在Haskell列表中,添加到右侧是昂贵的,添加到左侧是便宜的。

第二个解决方案从外到内或从左到右工作,这是此问题的正确执行顺序。