迭代器的连接

时间:2016-08-02 06:39:35

标签: algorithm scala binary-tree

我在“Scala编程”第24章“深入收集”中看到了这个例子。此示例显示了实现树的两种替代方法:

  1. 通过扩展Traversable[Int] - 此处def foreach[U](f: Int => U): Unit的复杂性为O(N)

  2. 通过扩展Iterable[Int] - 此处def iterator: Iterator[Int]的复杂性为O(N log(N))

  3. 这是为了说明为什么有两个独立的特征TraversableIterable会有所帮助。

      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}}遍历的成本。

    ????

    为什么连接迭代器的计算需要得到左或右迭代器的叶子?

2 个答案:

答案 0 :(得分:2)

"集合深入"很贴切。数据结构的深度很重要。

当您调用top.iterator.next()时,每个内部Branch都会委托给它下面的BranchNode的迭代器,一个log(N)的调用链。

您在每个next()上都会产生该调用链。

使用foreach,您只需访问一次BranchNode

编辑:不确定这是否有帮助,但这是一个热切地定位叶子但懒洋洋地产生值的例子。它会在旧版本的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)

在此实现中,最顶层的分支不知道其leftright子分支中有多少个元素。

因此,迭代器是使用除法和征服方法递归构建的,这种方法在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等。因此,增加了复杂性。

我希望这能回答你的问题。