使用Scala进行N树遍历会导致堆栈溢出

Don*_*uts 10 tree recursion scala traversal

我试图从N树数据结构返回一个小部件列表.在我的单元测试中,如果我有大约2000个小部件,每个小部件都有一个依赖,我会遇到堆栈溢出.我认为正在发生的是for循环导致我的树遍历不是尾递归.什么是在scala中写这个的更好方法?这是我的功能:

protected def getWidgetTree(key: String) : ListBuffer[Widget] = {
  def traverseTree(accumulator: ListBuffer[Widget], current: Widget) : ListBuffer[Widget] = {
    accumulator.append(current)

    if (!current.hasDependencies) {
      accumulator
    }  else {
      for (dependencyKey <- current.dependencies) {
        if (accumulator.findIndexOf(_.name == dependencyKey) == -1) {
          traverseTree(accumulator, getWidget(dependencyKey))
        }
      }

      accumulator
    }
  }

  traverseTree(ListBuffer[Widget](), getWidget(key))
}
Run Code Online (Sandbox Code Playgroud)

dhg*_*dhg 10

它不是尾递归的原因是你在函数内部进行了多次递归调用.要进行尾递归,递归调用只能是函数体中的最后一个表达式.毕竟,重点是它像while循环一样工作(因此,可以转换为循环).循环不能在单次迭代中多次调用自身.

要像这样进行树遍历,可以使用队列来转发需要访问的节点.

假设我们有这棵树:

//        1
//       / \  
//      2   5
//     / \
//    3   4
Run Code Online (Sandbox Code Playgroud)

用这个简单的数据结构表示:

case class Widget(name: String, dependencies: List[String]) {
  def hasDependencies = dependencies.nonEmpty
}
Run Code Online (Sandbox Code Playgroud)

我们有这个映射指向每个节点:

val getWidget = List(
  Widget("1", List("2", "5")),
  Widget("2", List("3", "4")),
  Widget("3", List()),
  Widget("4", List()),
  Widget("5", List()))
  .map { w => w.name -> w }.toMap
Run Code Online (Sandbox Code Playgroud)

现在我们可以将您的方法重写为tail-recursive:

def getWidgetTree(key: String): List[Widget] = {
  @tailrec
  def traverseTree(queue: List[String], accumulator: List[Widget]): List[Widget] = {
    queue match {
      case currentKey :: queueTail =>        // the queue is not empty
        val current = getWidget(currentKey)  // get the element at the front
        val newQueueItems =                  // filter out the dependencies already known
          current.dependencies.filterNot(dependencyKey => 
            accumulator.exists(_.name == dependencyKey) && !queue.contains(dependencyKey))
        traverseTree(newQueueItems ::: queueTail, current :: accumulator) // 
      case Nil =>                            // the queue is empty
        accumulator.reverse                  // we're done
    }
  }

  traverseTree(key :: Nil, List[Widget]())
}
Run Code Online (Sandbox Code Playgroud)

测试一下:

for (k <- 1 to 5)
  println(getWidgetTree(k.toString).map(_.name))
Run Code Online (Sandbox Code Playgroud)

打印:

ListBuffer(1, 2, 3, 4, 5)
ListBuffer(2, 3, 4)
ListBuffer(3)
ListBuffer(4)
ListBuffer(5)
Run Code Online (Sandbox Code Playgroud)