我在一本算法书中读到,Ackermann函数不能被尾递归(他们说的是“它不能转化为迭代”)。我对此非常困惑,所以我试着想出这个:
let Ackb m n =
let rec rAck cont m n =
match (m, n) with
| 0, n -> cont (n+1)
| m, 0 -> rAck cont (m-1) 1
| m, n -> rAck (fun x -> rAck cont (m-1) x) m (n-1)
in rAck (fun x -> x) m n
;;
(这是OCaml / F#代码)。
我的问题是,我不确定这实际上是尾递归。你能确认一下吗?如果没有,为什么?最后,当人们说Ackermann函数不是原始递归时,它意味着什么呢?
谢谢!
答案 0 :(得分:14)
是的,它是尾递归的。通过对Continuation Passing Style的显式转换,可以对每个函数进行尾部调整。
这并不意味着该函数将在常量内存中执行:您构建必须分配的延续堆栈。 defunctionalize将数据表示为简单代数数据类型的延续可能更有效。
存在primitive recursive是一个非常不同的概念,与数学理论中使用的某种形式的递归定义的表达有关,但可能与你所知的计算机科学无关:它们非常有用表达能力降低,具有功能组成的系统(从Gödel的System T开始),例如所有当前的编程语言,功能更强大。
就计算机语言而言,原始递归函数大致对应于没有一般递归的程序,其中所有循环/迭代都是静态有界的(可能的重复次数是已知的)。
答案 1 :(得分:2)
是的。
根据定义,任何递归函数都可以转换为迭代,只要它可以访问无界的类似堆栈的构造。有趣的问题是它是否可以在没有堆栈或任何其他无界数据存储的情况下完成。
只有当参数的大小有界时,尾递归函数才能转换为这样的迭代。在您的示例中(以及几乎任何使用continuation的递归函数),cont
参数用于所有方法和目的,可以增长到任何大小的堆栈。实际上,延续传递风格的整个要点是在延迟参数中存储通常存在于调用堆栈中的数据(“我返回后要做什么?”)。