Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
递归的替代使用如何影响Haskell列表反转的效率?_Haskell_Recursion - Fatal编程技术网

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

递归的替代使用如何影响Haskell列表反转的效率?,haskell,recursion,Haskell,Recursion,休息了很长时间后,我正在复习哈斯克尔。我通过s工作。我为#5写了一个解决方案,然后看了一眼,扩展了我的知识。以下是#5解决方案页面的摘录: 撤销一份清单 前奏曲中的标准定义是简洁的,但不是简单的 非常可读。定义反向的另一种方法是: reverse :: [a] -> [a] reverse [] = [] reverse (x:xs) = reverse xs ++ [x] 然而,这个定义比前奏曲中的定义更浪费 在累积结果时反复确认结果。以下 变异避免了这一点,因此在计算上更接近前奏 版

休息了很长时间后,我正在复习哈斯克尔。我通过s工作。我为#5写了一个解决方案,然后看了一眼,扩展了我的知识。以下是#5解决方案页面的摘录:

撤销一份清单

前奏曲中的标准定义是简洁的,但不是简单的 非常可读。定义反向的另一种方法是:

reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
然而,这个定义比前奏曲中的定义更浪费 在累积结果时反复确认结果。以下 变异避免了这一点,因此在计算上更接近前奏 版本

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

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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

除此之外还有
++<