简体   繁体   English

使用函数“ fold”从二叉树顺序遍历构建列表

[英]Build List from binary tree inorder traversal using function “fold”

I'm learning Scala. 我正在学习Scala。 Now I have this code snippet: 现在,我有以下代码片段:

sealed abstract class BSTree {
    def fold[A](init: A)(f: (A, Int) => A): A = this match {
      case Empty => init
      case Node(left, data, right) =>
        val curInorder:A = f(left.fold(init)(f), data)
        right.fold(curInorder)(f)
    }
}

case object Empty extends BSTree
case class Node(left: BSTree, data: Int, right: BSTree) extends BSTree

My aim is to add another method toList in class BSTree , which is on top of method fold and build a List from the binary tree's inorder traversal. 我的目标是在方法fold顶部向class BSTreeList添加另一个方法,并根据二叉树的有序遍历构建List

My current implementation is: 我当前的实现是:

sealed abstract class BSTree {
    def fold[A](init: A)(f: (A, Int) => A): = .....//code snippet skipped
    def toList: List[Int] =  
             fold(Nil: List[Int])((xs: List[Int], hd)=> hd::xs).reverse
}

But I feel that building a List and then reversing it is ugly. 但是我觉得建立一个List然后反转它是很丑陋的。 Is there a more elegant approach? 有没有更优雅的方法? Any hints are appreciated. 任何提示表示赞赏。

First of all, your fold is not tail recursive which for large input might result in StackOverflowException . 首先,您的折叠不是尾部递归,对于大量输入而言,这可能会导致StackOverflowException I'd encourage you to try out and implement it on your own using Stack . 我鼓励您尝试并使用Stack自己实现它。 For reference I'll place a sample implementation at the bottom of my post. 作为参考,我将在示例的底部放置一个示例实现。

Secondly, as it was already mentioned in comments - you might want to use ListBuffer so that building your list is more efficient in reversed order (thus, there is no need to reverse it back). 其次,正如注释中已经提到的那样-您可能要使用ListBuffer以便以反向顺序构建列表更加有效(因此,无需反向反向)。

Here's a one-liner: 这里是单线:

def toList: List[Int] = fold(ListBuffer.empty[Int])(_ += _).toList

And the the reference for implementing tail-recursive fold : 以及实现尾递归fold的参考:

def fold[B](init: B)(op: (B, A) => B): B = {
  def go(stack: List[(A, Tree[A])], current: Tree[A], acc: B): B = (current, stack) match {
    case (Empty, Nil) => acc
    case (Empty, (d, r) :: xs) => go(xs, r, op(acc, d))
    case (Node(l, d, r), _) => go((d, r) +: stack, l, acc)
  }

  go(Nil, this, init)
}

I find that simply using xs :+ hd instead of hd::xs puts the values in the correct order (depth-first, left-to-right). 我发现仅使用xs :+ hd而不是hd::xs将值按正确的顺序排列(深度优先,从左至右)。

val testTree: BSTree =
  Node(Node(Empty, 0, Empty), 1, Node(Empty, 2, Node(Node(Empty, 3, Empty), 4, Empty)))

def toList(bSTree: BSTree): List[Int] =
  bSTree.fold(List[Int]())((acc, next) => acc :+ next)

toList(testTree) // List(0,1,2,3,4)

My implementation above is O(n²) . 我在上面的实现是O(n²) We can improve it to O(n) by using a ListBuffer , as per @dkim's comment, or we can use Vector and then convert to List when we're done. 我们可以用它来改善O(n)的 ListBuffer ,按@ DKIM的评论,或者我们可以使用Vector然后转换为List时,我们就大功告成了。

Apart from simply fixing the toList method, we might ask why the result of using fold to implement toList didn't agree with our intuition (giving us a backwards list instead of a forwards list). 除了简单地修复toList方法之外,我们可能会问为什么使用fold来实现toList的结果与我们的直觉toList (为我们提供了向后列表而不是向前列表)。 One might point out that the fold signature for list matches the structure of the List class hierarchy. 可能会指出,列表的折叠签名与List类层次结构的结构匹配。

abstract class List[+A] {
  def fold[B](init: B)(step: (A, B) => B): B
}

case object Empty extends List[Nothing] {
  def fold[B](init: B)(step: (A, B) => B): B = init
}

case class Cons[+A](head: A, tail: List[A]) extends List[A] {
  def fold[B](init: B)(step: (A, B) => B): B =
    step(head, tail.fold(init)(step))
}

Notice how the method signature of fold matches the class hierarchy, even down to the values that each implementing class holds. 注意fold的方法签名如何与类层次结构匹配,甚至降至每个实现类所拥有的值。 (Aside: For purposes of brevity, I am using a very naive implementation of fold that is neither efficient nor stack safe. Production implementations should be tail recursive or use a loop and a mutable buffer, but the point is that the method signature would be the same.) (另外:为简洁起见,我使用的fold非常幼稚,既不高效也不安全。生产实现应为尾递归或使用循环和可变缓冲区,但要点是方法签名应为相同。)

We can do the same for your BSTree class, the fold signature would be: 我们可以对您的BSTree类执行相同的BSTreefold签名为:

abstract class BSTree {
  def fold[A](withEmpty: A)(withNode: (A, Int, A) => A): A
}

Then toList would be tree.fold(List[Int]())((l, n, r) => l ++ List(n) ++ r) . 那么toList将是tree.fold(List[Int]())((l, n, r) => l ++ List(n) ++ r) But again, use a buffer or Vector to get decent performance if you anticipate tree being even about 50 entries or so. 但是同样,如果您预期tree大约有50个左右的条目,请使用缓冲区或Vector获得不错的性能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM