以功能方式遍历树

时间:2016-01-27 13:54:09

标签: algorithm scala data-structures tree functional-programming

我在Scala中实现了一个基本的可变树,我想以功能的方式遍历它以搜索一个元素,但我不知道如何实现它。如果可能的话,我也希望算法是尾递归的。

树是一个带有值的结构和一个也是树的叶子列表。

任何帮助都将不胜感激。

这是我的代码(专注于getOpt方法):

package main

import scala.collection.mutable.ListBuffer

sealed trait Tree[Node] {

  val node: Node

  val parent: Option[Tree[Node]]

  val children: ListBuffer[Tree[Node]]

  def add(n: Node): Tree[Node]

  def size: Int

  def getOpt(p: (Node) => Boolean): Option[Tree[Node]]

  override def toString = {
    s"""[$node${if (children.isEmpty) "" else s", Children: $children"}]"""
  }
}

case class ConcreteTree(override val node: Int) extends Tree[Int] {

  override val children = ListBuffer[Tree[Int]]()

  override val parent: Option[Tree[Int]] = None

  override def add(n: Int): ConcreteTree = {
    val newNode = new ConcreteTree(n) {override val parent: Option[Tree[Int]] = Some(this)}
    children += newNode
    newNode
  }

  override def size: Int = {
    def _size(t: Tree[Int]): Int = {
      1 + t.children.foldLeft(0)((sum, tree) => sum + _size(tree))
    }
    _size(this)
  }

  // This method is not correct
  override def getOpt(p: (Int) => Boolean): Option[Tree[Int]] = {
    def _getOpt(tree: Tree[Int], p: (Int) => Boolean): Option[Tree[Int]] = {
      tree.children.map {
        t =>
          if(p(t.node)) Some(t) else t.children.map(_getOpt(_, p))
      }
    }
  }
}

object Main {

  def main(args: Array[String]) {
    val tree1 = ConcreteTree(1)
    val tree2 = tree1.add(2)
    val tree3 = tree1.add(3)

    println(tree1.getOpt(_ == 2))
  }
}

@doertsch回答是我目前最好的方法。

4 个答案:

答案 0 :(得分:4)

我实际上会寻求更灵活的东西并实现一个通用函数来生成扁平树的惰性流,然后你的很多后期工作将变得更加容易。像这样:

def traverse[Node](tree: Tree[Node]): Stream[Tree[Node]] = 
  tree #:: (tree.children map traverse).fold(Stream.Empty)(_ ++ _)

然后您的getOpt缩小为:

override def getOpt(p: (Int) => Boolean): Option[Tree[Int]] =
  traverse(tree) find {x => p(x.node)}

进一步简化,如果您只对没有Tree结构的数据感兴趣,您可以获得一个节点流,为您提供:

def nodes[Node](tree: Tree[Node]): Stream[Node] = traverse(tree) map (_.node)

def getNode(p: (Int) => Boolean): Option[Int] = nodes(tree) find p

这为非常简洁易读的代码提供了其他可能性,例如nodes(tree) filter (_ > 3)nodes(tree).sumnodes(tree) contains 12和类似的表达式。

答案 1 :(得分:2)

使用方法existsfind(每List提供一次),您可以实现“在找到结果时完成”行为。 (虽然可能在内部辩论,但这些并没有以完全功能的方式实现:https://github.com/scala/scala/blob/5adc400f5ece336f3f5ff19691204975d41e652e/src/library/scala/collection/LinearSeqOptimized.scala#L88

您的代码可能如下所示:

case class Tree(nodeValue: Long, children: List[Tree]) {

  def containsValue(search: Long): Boolean =
    search == nodeValue || children.exists(_.containsValue(search))

  def findSubTreeWithNodeValue(search: Long): Option[Tree] =
    if (search == nodeValue)
      Some(this)
    else
      children.find(_.containsValue(search)).
        flatMap(_.findSubTreeWithNodeValue(search))
}

在最后两行中,find应用程序将返回当前节点的正确子树,如果存在,则flatMap部分将递归地从中提取正确的子树,或者离开如果找不到值,则None结果不变。

然而,这个具有两次遍历部分的不可思议的特征,一次用于确定结果是否存在,一次用于从包含它的树中提取它。可能有一种方法可以更有效地解决这个问题,但我现在无法解决这个问题......

答案 2 :(得分:2)

我想,你正在寻找这样的东西:

@tailrec
def findInTree[T](value: T, stack: List[Node[T]]): Option[Node[T]] = stack match {
   case Nil => None
   case Node(value) :: _ => stack.headOption
   case head :: tail => findInTree(value, head.children ++ tail)
}

这不会遍历树的两部分,也是尾递归的。 为了清晰起见,我稍微更改了您的课程定义,但它的想法相同。 您可以这样称呼它:findInTree(valueToFind, List(root))

答案 3 :(得分:1)

将集合转换为view时,所有变换器(如map)都会被懒散地实现。这意味着元素仅在需要时处理。这应该可以解决您的问题:

override def getOpt(p: (Int) => Boolean): Option[Tree[Int]] = {
  if (p(node)) Some(this)
  else children.view.map(_.getOpt(p)).find(_.isDefined).getOrElse(None)
}

因此,我们将(懒惰地)映射到子项上,将它们转换为搜索节点的Option s。随后我们发现第一个Option不是None。由于getOrElse(None)本身会返回find,因此需要最后Option来“展平”嵌套选项。

我实际上没有运行代码,所以请原谅小错误。但是,一般方法应该变得清晰。