我在“Scala编程”第24章“深入收集”中看到了这个例子。此示例显示了实现树的两种替代方法:
通过扩展Traversable[Int]
- 此处def foreach[U](f: Int => U): Unit
的复杂性为O(N)
。
通过扩展Iterable[Int]
- 此处def iterator: Iterator[Int]
的复杂性为O(N log(N))
。
这是为了说明为什么有两个独立的特征Traversable
和Iterable
会有所帮助。
sealed abstract class Tree
case class Branch(left: Tree, right: Tree) extends Tree
case class Node(elem: Int) extends Tree
sealed abstract class Tree extends Traversable[Int] {
def foreach[U](f: Int => U) = this match {
case Node(elem) => f(elem)
case Branch(l, r) => l foreach f; r foreach f
}
}
sealed abstract class Tree extends Iterable[Int] {
def iterator: Iterator[Int] = this match {
case Node(elem) => Iterator.single(elem)
case Branch(l, r) => l.iterator ++ r.iterator
}
}
关于foreach
的实施,他们说:
遍历平衡树需要的时间与数量成正比 树中的元素。要看到这一点,请考虑使用平衡树 使用
N
离开时,您将拥有类Branch的N - 1
个内部节点。所以 遍历树的步骤总数为N + N - 1
。
这是有道理的。 :)
但是,他们提到iterator
方法中两个迭代器的连接时间复杂度为log(N)
,因此方法的总复杂度为N log(N)
:
每次元素由连接的迭代器生成时,例如
l.iterator ++ r.iterator
,计算需要遵循一个 间接获取正确的迭代器(l.iterator
或者r.iterator
)。总的来说,这使得log(N)
间接得到了支持 N叶的平衡树。因此,访问树的所有元素的成本从2N
遍历方法的大约foreach
上升到N log(N)
的{{1}}遍历的成本。
????
为什么连接迭代器的计算需要得到左或右迭代器的叶子?
答案 0 :(得分:2)
"集合深入"很贴切。数据结构的深度很重要。
当您调用top.iterator.next()
时,每个内部Branch
都会委托给它下面的Branch
或Node
的迭代器,一个log(N)
的调用链。
您在每个next()
上都会产生该调用链。
使用foreach
,您只需访问一次Branch
或Node
。
编辑:不确定这是否有帮助,但这是一个热切地定位叶子但懒洋洋地产生值的例子。它会在旧版本的Scala中堆栈溢出或速度较慢,但链式++
的实现得到了改进。现在它是一条随着消耗而变短的扁平链。
sealed abstract class Tree extends Iterable[Int] {
def iterator: Iterator[Int] = {
def leafIterator(t: Tree): List[Iterator[Int]] = t match {
case Node(_) => t.iterator :: Nil
case Branch(left, right) => leafIterator(left) ::: leafIterator(right)
}
this match {
case n @ Node(_) => Iterator.fill(1)(n.value)
case Branch(left @ Node(_), right @ Node(_)) => left.iterator ++ right.iterator
case b @ Branch(_, _) =>
leafIterator(b).foldLeft(Iterator[Int]())((all, it) => all ++ it)
}
}
}
case class Branch(left: Tree, right: Tree) extends Tree {
override def toString = s"Branch($left, $right)"
}
case class Node(elem: Int) extends Tree {
def value = {
Console println "An expensive leaf calculation"
elem
}
override def toString = s"Node($elem)"
}
object Test extends App {
// many leaves
val n = 1024 * 1024
val ns: List[Tree] = (1 to n).map(Node(_)).toList
var b = ns
while (b.size > 1) {
b = b.grouped(2).map { case left :: right :: Nil => Branch(left, right) }.toList
}
Console println s"Head: ${b.head.iterator.take(3).toList}"
}
答案 1 :(得分:0)
在此实现中,最顶层的分支不知道其left
和right
子分支中有多少个元素。
因此,迭代器是使用除法和征服方法递归构建的,这种方法在iterator
方法中清晰地表示 - 你到达每个节点(case Branch
),你生成单个节点case Node => ...
的迭代器,然后加入它们。
如果没有进入每个节点,它就不会知道有哪些元素以及树是如何构造的(允许奇数分支与不允许等等)。
编辑:
让我们看看++
上的Iterator
方法。
def ++[B >: A](that: => GenTraversableOnce[B]): Iterator[B] = new Iterator.JoinIterator(self, that)
然后在Iterator.JoinIterator
private[scala] final class JoinIterator[+A](lhs: Iterator[A], that: => GenTraversableOnce[A]) extends Iterator[A] {
private[this] var state = 0 // 0: lhs not checked, 1: lhs has next, 2: switched to rhs
private[this] lazy val rhs: Iterator[A] = that.toIterator
def hasNext = state match {
case 0 =>
if (lhs.hasNext) {
state = 1
true
} else {
state = 2
rhs.hasNext
}
case 1 => true
case _ => rhs.hasNext
}
def next() = state match {
case 0 =>
if (lhs.hasNext) lhs.next()
else {
state = 2
rhs.next()
}
case 1 =>
state = 0
lhs.next()
case _ =>
rhs.next()
}
override def ++[B >: A](that: => GenTraversableOnce[B]) =
new ConcatIterator(this, Vector(() => that.toIterator))
}
从中可以看出,加入迭代器只会在rhs
字段中创建一个递归结构。此外,让我们更多地关注它。
考虑具有结构level1 [A]; level2 [B][C]; level 3[D][E][F][F]
在迭代器上调用JoinIterator
时,保留现有的lhs
迭代器。但是,您始终.toIterator
rhs
。这意味着对于每个后续级别,将重建rhs
部分。因此,对于B ++ C
,您会看到A.lhs (stands for B)
和A.rhs (stands for C.toIterator)
,其中C.toIterator
代表C.lhs and C.rhs
等。因此,增加了复杂性。
我希望这能回答你的问题。