微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

要求我将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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。