二叉树尾的Scala sum是递归的

时间:2014-10-22 01:31:32

标签: scala recursion tree binary-tree tail-recursion

所以我有以下代码来定义二叉树。

sealed abstract class BinTree {
  def sum = sumAcc(0)
  def sumAcc(acc: Int): Int
}

case class NonEmpty(val elem: Int, val left: BinTree, val right: BinTree) extends BinTree {
  def sumAcc(acc: Int) = right.sumAcc(left.sumAcc(elem + acc))
}

case object Empty extends BinTree {
  def sumAcc(acc: Int) = acc
}

val rootTree = NonEmpty(1, NonEmpty(2, NonEmpty(3, Empty, Empty), Empty), Empty)
rootTree.sum

sum方法尾递归吗?我怀疑尾部不是递归的,因为对right.sumAcc的调用必须等到left.sumAcc(elem + acc)才能终止。

如果它不是尾递归,我怎么能改变呢?

2 个答案:

答案 0 :(得分:3)

好的,所以在评论中指出solution我重写了尾递归的答案。我在辅助方法中使用了累加器和隐式堆栈。通过注释,我们可以检查实际上该方法是尾递归的。

以下是新代码,以防有人发现它有用。

import scala.annotation.tailrec

sealed abstract class BinTree
case object Empty extends BinTree
case class NonEmpty(val elem: Int, val left: BinTree, val right: BinTree) extends BinTree

val rootTree = NonEmpty(1, NonEmpty(2, NonEmpty(3, Empty, Empty), Empty), Empty)

def sumTailRec(bt: BinTree) = {
  @tailrec
  def sumAccStack(trees: List[BinTree], acc: Int): Int = trees match {
    case Nil => acc
    case Empty :: rs => sumAccStack(rs, acc)
    case NonEmpty(e, l, r) :: rs => sumAccStack(l :: r :: rs, e + acc)
  }

  sumAccStack(List(bt), 0)
}

sumTailRec(rootTree)

答案 1 :(得分:0)

在你的情况下,函数不是尾递归,但原因有点不同 - 没有等待,程序按顺序执行,left.sumAccright.sumAcc之前进行评估,但这仍然是一个问题,因为left.sumAcc调用是重复的,但不在尾部位置。即使你要删除它,你的解决方案还有其他问题。

要检查是否可以对函数应用尾递归,可以使用the @tailrec annotation。在您的情况下,编译器错误消息将是:

  

错误:(11,7)无法优化@tailrec带注释的方法sumAcc:它既不是私有的也不是最终的,所以可以被覆盖

     

def sumAcc(acc:Int)= right.sumAcc(left.sumAcc(elem + acc))

为什么会发生这种情况,请参阅Why won't the Scala compiler apply tail call optimization unless a method is final?问题:

  

除非方法标记为final,否则在进行递归调用时可能不会自行调用

如果您尝试将final添加到NonEmpty sumAcc,这也是编译器会告诉您的内容。错误消息将更改为:

  

错误:(11,38)无法优化@tailrec带注释的方法sumAcc:它包含一个递归调用,目标是超类型NonEmpty.this.right.type

     

final def sumAcc(acc:Int)= right.sumAcc(left.sumAcc(elem + acc))

solution注释中链接的Millie Smith通过不使用重写来实现函数来克服这一点,而是使用模式匹配(即函数中的类型检查),因此一个函数正在处理对LeafNode类型的遍历进行遍历,并且该函数的结构使得在每种情况下只能对sumAcc进行一次调用。