长度函数的尾递归版本是否在运行时节省了堆栈空间?

问题描述

要求我将F#length函数更改为尾递归函数。

let rec len xs = 
  match xs with 
    | [] -> 0 
    | x::xr -> 1 + len xr;;

虽然这不是一个困难的练习,但我想知道是否更改为尾递归版本,例如下面的版本,

let rec leni xs r = 
  match xs with 
    | [] -> r 
    | x::xr -> leni xr r+1;;

与非尾递归相比,确实在运行时节省了堆栈空间?

解决方法

尾递归函数被编译成一个循环,它们不使用任何其他依赖于迭代次数的堆栈。

A,您的版本不是尾递归的,因为您将运算符的优先级设置为错误。累加器r被解释为属于递归调用,它未更改地传递给该递归调用。因此,该函数需要返回以增加其返回值。

让我们看看:

let rec len xs = 
  match xs with 
    | [] -> 0 
    | x::xr -> 1 + len xr;;

let rec leni xs r = 
  match xs with 
    | [] -> r 
    | x::xr -> leni xr r+1;;

[0..10000] |> len     // val it : int = 10001
[0..100000] |> len    // val it : int = 100001
[0..1000000] |> len   // Process is terminated due to StackOverflowException.

([0..1000000],0) ||> leni   // Process is terminated due to StackOverflowException.

解决方法是简单地将新的累加器值括在括号中,并将其加1。

let rec leni' xs r = 
  match xs with 
    | [] -> r 
    | x::xr -> leni' xr (r+1)

([0..1000000],0) ||> leni'   // val it : int = 1000001

您可以继续使用Continuation Passing Style(CPS),用组合函数代替累加器,每个累加函数的自变量都加1。这也将编译成一个循环并保留堆栈空间,但以存储功能链所需的内存为代价。

此外,您可以重新考虑参数的顺序:首先使用累加器(或继续符),最后使用列表,以允许使用function关键字。

let rec lenk k = function
| [] -> k 0 
| x::xr -> lenk (k << (+) 1) xr

[0..1000000] |> lenk id      // val it : int = 1000001

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...