所以我有以下代码来定义二叉树。
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)
才能终止。
如果它不是尾递归,我怎么能改变呢?
答案 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.sumAcc
在right.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通过不使用重写来实现函数来克服这一点,而是使用模式匹配(即函数中的类型检查),因此一个函数正在处理对Leaf
和Node
类型的遍历进行遍历,并且该函数的结构使得在每种情况下只能对sumAcc
进行一次调用。