二叉树的尾递归函数
Tail recursive functions for BinaryTree
我一直坚持为非常简单的二叉树实现实现尾递归 foreach、reduce、map 和 toList 函数。
sealed trait Tree[+A]
case object EmptyTree extends Tree[Nothing]
case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]
object Tree {
def apply[A]: Tree[A] = EmptyTree
def apply[A](value: A): Tree[A] = Node(value, EmptyTree, EmptyTree)
def apply[A](value: A, left: Tree[A], right: Tree[A]): Tree[A] = Node(value, left, right)
def foreach[A](tree: Tree[A], f: (A) => Unit): Unit = {
//@tailrec
def iter[A](tree: Tree[A], f: (A) => Unit): Unit = tree match {
case EmptyTree =>
case Node(v, l, r) =>
iter(l, f)
f(v)
iter(r, f)
}
iter(tree, f)
}
def reduce[A](tree: Tree[A], value: A, f: (A, A) => A): A = {
//@tailrec
def loop(tree: Tree[A], value: A): A = tree match {
case Node(v, l, r) => loop(l, f(loop(r, value), v))
case EmptyTree => value
}
loop(tree, value)
}
def map[A, B](tree: Tree[A], f: A => B): Tree[B] = {
//@tailrec
def iter[A](tree: Tree[A], f: A => B): Tree[B] = tree match {
case Node(v, l, r) => Node(f(v), iter(l, f), iter(r, f))
case EmptyTree => EmptyTree
}
iter(tree, f)
}
def toList[A](t: Tree[A]): List[A] = {
//@tailrec
def iter[A](t: Tree[A]): List[A] = t match {
case Node(v, l, r) => v :: iter(l) ::: iter(r)
case EmptyTree => List.empty
}
iter(t)
}
}
测试代码:
val tree = Tree(1, Tree(2, Tree(3), Tree(4)), Tree(5, Tree(6), Tree(7)))
Tree.foreach(tree, (x: Int) => println(x))
Tree.reduce(tree, 0, (x: Int, y: Int) => x + y)
Tree.map(tree, (x: Int) => x + 1)
Tree.toList(tree)
我不能使用@tailrec 属性,因为如您所见,递归调用不是函数中的最后一次调用,而且我不知道如何重写它,因为一个函数中有多个调用,例如
v :: iter(l) ::: iter(r)
我知道我可以将累加器用于内部递归函数,但在多次调用的情况下我应该如何使用它?
提前致谢。
已更新:
def toListRec[A](tree: Tree[A]): List[A] = {
@tailrec
def iter(result: List[A], todo: List[Tree[A]]): List[A] = todo match {
case x :: tail => x match {
case Node(v, l, r) => iter(v :: result, l :: r :: tail)
case EmptyTree => iter(result, tail)
}
case Nil => result.reverse
}
iter(List.empty, List(tree))
}
没有尾递归,一个(/the) 堆栈用于跟踪调用函数。如果您想使用尾递归,则必须找到一种方法在其他地方跟踪此信息。在更简单的 "linear" 情况下,例如阶乘,此信息非常有限,通常可以使用累加器轻松处理。
在你的例子中,问题是递归不是线性的。在一次递归调用后,该函数不仅计算结果,而且在能够得到结果之前进行另一次递归调用。
为了在这种情况下应用尾递归,您必须明确跟踪必须进行的剩余递归调用。一种简单的方法是简单地保留一个 "to-do" 列表。例如:
def toList[A](t: Tree[A]): List[A] = {
@tailrec def iter[A](todo: List[Tree[A]], r: List[A]): List[A] =
todo match {
case t :: rest => t match {
case Node(v, l, r) => iter(l :: r :: rest, v :: r)
case EmptyTree => iter(rest, r)
}
case List.empty => reverse(r)
}
iter(List(t), List.empty)
}
免责声明:我对scala一无所知。 :)
mweerden 建议的解决方案可行,但是,还有另一种解决问题的方法,我认为这种方法更优雅。这是遍历树以列出
的代码
def toList[T](t: Tree[T]): List[T] = {
def tailRecursive(tree: Tree[T], acc: List[T]): List[T] = tree match {
case EmptyTree => acc
case Node(value, right, left) =>
tailRecursive(left, value :: tailRecursive(right, acc))
}
tailRecursive(t, List())
}
解法暗示该树为二叉搜索树,产生的列表会按升序排列(如果不需要升序,第6行可以改,把值放在第一次递归调用的前面或者直接进入蓄能器是可能的)。
我一直坚持为非常简单的二叉树实现实现尾递归 foreach、reduce、map 和 toList 函数。
sealed trait Tree[+A]
case object EmptyTree extends Tree[Nothing]
case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]
object Tree {
def apply[A]: Tree[A] = EmptyTree
def apply[A](value: A): Tree[A] = Node(value, EmptyTree, EmptyTree)
def apply[A](value: A, left: Tree[A], right: Tree[A]): Tree[A] = Node(value, left, right)
def foreach[A](tree: Tree[A], f: (A) => Unit): Unit = {
//@tailrec
def iter[A](tree: Tree[A], f: (A) => Unit): Unit = tree match {
case EmptyTree =>
case Node(v, l, r) =>
iter(l, f)
f(v)
iter(r, f)
}
iter(tree, f)
}
def reduce[A](tree: Tree[A], value: A, f: (A, A) => A): A = {
//@tailrec
def loop(tree: Tree[A], value: A): A = tree match {
case Node(v, l, r) => loop(l, f(loop(r, value), v))
case EmptyTree => value
}
loop(tree, value)
}
def map[A, B](tree: Tree[A], f: A => B): Tree[B] = {
//@tailrec
def iter[A](tree: Tree[A], f: A => B): Tree[B] = tree match {
case Node(v, l, r) => Node(f(v), iter(l, f), iter(r, f))
case EmptyTree => EmptyTree
}
iter(tree, f)
}
def toList[A](t: Tree[A]): List[A] = {
//@tailrec
def iter[A](t: Tree[A]): List[A] = t match {
case Node(v, l, r) => v :: iter(l) ::: iter(r)
case EmptyTree => List.empty
}
iter(t)
}
}
测试代码:
val tree = Tree(1, Tree(2, Tree(3), Tree(4)), Tree(5, Tree(6), Tree(7)))
Tree.foreach(tree, (x: Int) => println(x))
Tree.reduce(tree, 0, (x: Int, y: Int) => x + y)
Tree.map(tree, (x: Int) => x + 1)
Tree.toList(tree)
我不能使用@tailrec 属性,因为如您所见,递归调用不是函数中的最后一次调用,而且我不知道如何重写它,因为一个函数中有多个调用,例如
v :: iter(l) ::: iter(r)
我知道我可以将累加器用于内部递归函数,但在多次调用的情况下我应该如何使用它?
提前致谢。
已更新:
def toListRec[A](tree: Tree[A]): List[A] = {
@tailrec
def iter(result: List[A], todo: List[Tree[A]]): List[A] = todo match {
case x :: tail => x match {
case Node(v, l, r) => iter(v :: result, l :: r :: tail)
case EmptyTree => iter(result, tail)
}
case Nil => result.reverse
}
iter(List.empty, List(tree))
}
没有尾递归,一个(/the) 堆栈用于跟踪调用函数。如果您想使用尾递归,则必须找到一种方法在其他地方跟踪此信息。在更简单的 "linear" 情况下,例如阶乘,此信息非常有限,通常可以使用累加器轻松处理。
在你的例子中,问题是递归不是线性的。在一次递归调用后,该函数不仅计算结果,而且在能够得到结果之前进行另一次递归调用。
为了在这种情况下应用尾递归,您必须明确跟踪必须进行的剩余递归调用。一种简单的方法是简单地保留一个 "to-do" 列表。例如:
def toList[A](t: Tree[A]): List[A] = {
@tailrec def iter[A](todo: List[Tree[A]], r: List[A]): List[A] =
todo match {
case t :: rest => t match {
case Node(v, l, r) => iter(l :: r :: rest, v :: r)
case EmptyTree => iter(rest, r)
}
case List.empty => reverse(r)
}
iter(List(t), List.empty)
}
免责声明:我对scala一无所知。 :)
mweerden 建议的解决方案可行,但是,还有另一种解决问题的方法,我认为这种方法更优雅。这是遍历树以列出
的代码def toList[T](t: Tree[T]): List[T] = {
def tailRecursive(tree: Tree[T], acc: List[T]): List[T] = tree match {
case EmptyTree => acc
case Node(value, right, left) =>
tailRecursive(left, value :: tailRecursive(right, acc))
}
tailRecursive(t, List())
}
解法暗示该树为二叉搜索树,产生的列表会按升序排列(如果不需要升序,第6行可以改,把值放在第一次递归调用的前面或者直接进入蓄能器是可能的)。