BinaryTree的尾递归函数

时间:2016-08-13 08:22:38

标签: scala functional-programming tail-recursion

我很难实现tail递归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))
}

2 个答案:

答案 0 :(得分:9)

没有尾递归,(/)堆栈用于跟踪调用函数。如果你想使用尾递归,你必须找到一种在其他地方跟踪这些信息的方法。在更简单的“线性”情况下,例如阶乘,这些信息非常有限,通常可以通过累加器轻松处理。

在您的情况下,问题是递归不是线性的。在一次递归调用之后,该函数不只是计算结果,而是在能够获得结果之前进行另一次递归调用。

为了在这种情况下应用尾递归,您必须明确地跟踪必须进行的剩余递归调用。一种简单的方法是简单地保留“待办事项”列表。例如:

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一无所知。 :)

答案 1 :(得分:-2)

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行,将值放在第一次递归调用之前或直接进入累加器是可能的。)