我编写了以下方法来打印任意的arity树结构。 (深度搜索)
def treeString[A](tree: Tree[A]): String = {
def DFS(t: Tree[A], acc: String = "", visited: List[String] = Nil, toClose: Int = 0): String = t match {
case Leaf(id, terminal) if !visited.contains(id) => {
acc + " " + terminal.name + "[" + id.toString + "] " + ")"*toClose
}
case Node(id, op, args @_*) if !visited.contains(id) => {
var state = acc + " " + op.name + "[" + id.toString + "] " + "("
var args_visited: List[String] = List(id)
for (i <- args.tail) {
state = DFS(i, state , visited ::: args_visited) + " , "
args_visited = args_visited ++ List(i.id)
}
DFS(args.head, state , visited ++ args_visited, toClose + 1)
}
case _ => acc
}
DFS(tree)
}
scala编译器声称此函数不是tail recursive
。但是在尾部位置,我有适当的DFS(..)
电话。在循环中进行的所有DFS(..)
调用都将以迭代方式完成,因此堆栈安全。
据我所知,如果树无限深,则会发生堆栈溢出。我们有技术处理这些案件吗?
答案 0 :(得分:2)
我必须同意@VictorMoroz。问题是你的陈述:
state = DFS(i, state , visited ::: args_visited) + " , "
不在尾部位置。它确实创建了一个新的堆栈,因此,你的方法不再是尾递归了。
在没有深入研究数据结构的情况下,这里采用DFS方式以递归方式遍历树。
sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Node[A](left: Tree[A], right: Tree[A]) extends Tree[A]
case class Element(id:Int, name:String)
def DFS[T](t:Tree[T]): List[Element] = {
@tailrec
def _dfs(rest:List[Tree[T]], visited:List[Element]):List[Element] = {
rest match {
case Leaf(v:Element) => v :: visited
case Node(l,r) :: rs => _dfs(l::r::rs,visited)
}
}
_dfs(List(t),Nil)
}
关键是使用显式堆栈 - 这里以List的形式。
我明白如果树无限深,堆栈溢出就会 发生。
这是对的。基本上,在大多数编程语言中,每个方法调用都会保留一些堆栈内存。简单来说,tail-recursion允许编译器忽略先前的状态 - 不需要前一个堆栈。
请注意,DFS无法确保找到global optima,如果应该满足,则不应使用。
Postscriptum:
@tailrec
注释是编译器的友好提示,用于检查编译时是否可以优化您的方法(默认情况下Scala会进行尾调用)。
答案 1 :(得分:1)
我设法达到尾递归状态。
尽管代码优雅但我真正质疑功能方法的成本/收益。大多数时候,如何通过算法手动传递堆栈并不明显。
无论如何,这里的真正问题在于JVM的递归问题,scala编译器仍试图通过尾递归功能为我们提供一个转义,但是通常是让事情变得有效的噩梦
def treeString[A](tree: Tree[A]): String = {
def dropWhile[A](l: List[A], f: A => Boolean): List[A] =
l match {
case h :: t if f(h) => dropWhile(t, f)
case _ => l
}
@tailrec
def DFS(toVisit: List[Tree[A]], visited: List[String] = Nil, acc: String = "", n_args: List[Int] = Nil): String = toVisit match {
case Leaf(id, terminal) :: tail if !visited.contains(id) => {
val n_args_filtered = dropWhile[Int](n_args, x => x == 1)
val acc_to_pass = acc + " " + terminal.name + "[" + id.toString + "] " + ")" * (n_args.length - n_args_filtered.length) + "," * {if (n_args_filtered.length > 0) 1 else 0}
val n_args_to_pass = {if (n_args_filtered.length > 0 )List(n_args_filtered.head - 1) ::: n_args_filtered.tail else Nil}
DFS(toVisit.tail, visited ::: List(id), acc_to_pass, n_args_to_pass)
}
case Node(id, op, args @_*) :: tail if !visited.contains(id) => {
DFS(args.toList ::: toVisit.tail, visited ::: List(id), acc + " " + op.name + " (", List(args.length ) ::: n_args)
}
case Nil => acc
}
DFS(List(tree))
}