我在scala中创建了一个自定义对象树,而我的insert方法会抛出堆栈溢出,因为它不是尾递归的。但是,我无法弄清楚如何使其尾递归。相关的例子我见过使用“累加器”变量,但是它们或者像Integers这样的东西可以被乘法和覆盖,或者我无法适应树的列表。这就是我所拥有的:
我树木的基础:
abstract class GeoTree
case object EmptyTree extends GeoTree
case class Node(elem:GeoNode, left:GeoTree, right:GeoTree) extends GeoTree
以递归方式创建树的insert方法(导致堆栈溢出的方法):
def insert(t:GeoTree, v: GeoNode): GeoTree = t match {
case EmptyTree => new Node(v, EmptyTree, EmptyTree)
case Node(elem:GeoNode, left:GeoTree, right:GeoTree) => {
if (v < elem) new Node(elem, insert(left, v), right)
else new Node(elem, left, insert(right, v))
}
}
我认为GeoNode
的代码实际上并不特别重要,因为它非常简单。该类有两个Long
属性,<
,>
和==
运算符适当地重写以在树中使用。有人可以提出如何为我的insert
函数使用累加器的建议,或者其他一些方法来使其递归递归吗?
答案 0 :(得分:14)
您的函数不能是尾递归。原因是您对insert
的递归调用并未结束计算,它们被用作子表达式,在本例中为new Node(...)
。例如。如果你只是在搜索底部元素,那么很容易使它成为尾递归。
发生了什么:当你下降树时,在每个节点上调用insert
,但你必须记住返回root的方式,因为你必须用新值替换底部叶子后重建树。
可能的解决方案:明确记住下行路径,而不是堆栈。让我们使用一个简化的数据结构作为例子:
sealed trait Tree;
case object EmptyTree extends Tree;
case class Node(elem: Int, left:Tree, right:Tree) extends Tree;
现在定义路径是什么:如果我们向右或向左移动,它是一个节点列表以及信息。根始终位于列表的末尾,即开头的叶子。
type Path = List[(Node, Boolean)]
现在我们可以创建一个尾递归函数来计算给定值的路径:
// Find a path down the tree that leads to the leaf where `v` would belong.
private def path(tree: Tree, v: Int): Path = {
@tailrec
def loop(t: Tree, p: Path): Path =
t match {
case EmptyTree => p
case n@Node(w, l, r) =>
if (v < w) loop(l, (n, false) :: p)
else loop(r, (n, true) :: p)
}
loop(tree, Nil)
}
和一个获取路径和值的函数,并使用值作为路径底部的新节点重建新树:
// Given a path reconstruct a new tree with `v` inserted at the bottom
// of the path.
private def rebuild(path: Path, v: Int): Tree = {
@tailrec
def loop(p: Path, subtree: Tree): Tree =
p match {
case Nil => subtree
case (Node(w, l, r), false) :: q => loop(q, Node(w, subtree, r))
case (Node(w, l, r), true) :: q => loop(q, Node(w, l, subtree))
}
loop(path, Node(v, EmptyTree, EmptyTree))
}
插入很简单:
def insert(tree: Tree, v: Int): Tree =
rebuild(path(tree, v), v)
请注意,此版本效率不高。可能你可以使用Seq
提高效率,甚至可以通过使用可变堆栈来存储路径。但是List
可以很好地表达这个想法。
免责声明:我只编译了代码,我根本没有测试过。
备注:强>