用迭代器与组合模式对树进行遍历
来源:优易学  2011-12-17 12:17:08   【优易学:中国教育考试门户网】   资料下载   IT书店

  相信大家对迭代器模式还是比较熟悉的,在Java的集合中有比较多的应用。比如你想使用迭代器遍历一个集合,代码可能是这样:
  for (Iterator it = collection.iterator(); it.hasNext();)
  {
  doSomething(it.next());
  }
  迭代器的作用在于对数据的遍历与数据的内部表示进行了分离。通过迭代器,你知道调用hasNext()来确认是否还有下一个元素。如果有,那么就可以调用next()取得下一个元素。至于数据内部如何表示,我们并不关心。我们关心的只是能以一种预先定义的顺序来访问每个元素。
  组合模式用来表示一个节点内部包含若干个其它的节点,而被包含的节点同样也可以再包含另外的节点。因此是一种递归的结构。不包含其他节点的节点叫做叶子节点,这通常是递归的终结点。树形结构比较适合用来说明组合模式。
  假设我们用Node接口表示一个节点。可以往这个节点上添加节点,也可以获得这个节点的迭代器,用来遍历节点中的其他节点。
  public interface Node {
  void addNode(Node node);
  Iterator<Node> iterator();
  }
  抽象类AbstractNode实现这个接口,并不做多余的事情。只是让这个节点有个名字,以区分于其他节点。
  public abstract class AbstractNode implements Node {
  protected String name;
  protected AbstractNode(String name)
  {
  this.name = name;
  }
  public String toString()
  {
  return name;
  }
  }
  接下来,是表示两种不同类型的节点,树枝节点和叶子节点。它们都继承自AbstractNode,不同的是叶子节点没有子节点。
  public class LeafNode extends AbstractNode {
  public LeafNode(String name) {
  super(name);
  }
  public void addNode(Node node) {
  throw new UnsupportedOperationException("Can’t add a node to leaf.");
  }
  public Iterator<Node> iterator() {
  return new NullIterator<Node>();
  }
  }
  可以看到,试图往叶子节点添加节点会导致异常抛出。而获取叶子节点的迭代器,则返回……NullIterator。这个是什么?因为叶子节点没有子节点,所以叶子节点的迭代器没有什么意义。我们可以简单地返回null,但是那样处理节点的代码就需要区分是叶子节点还是其他节点。如果我们返回的是一个NullIterator,一个看起来和其他的迭代器差不多,但是hasNext()永远返回false,而next()永远返回null的迭代器,那么叶子节点的遍历也就和其他节点没有什么两样了。
  接下来是树枝节点:
  public class BranchNode extends AbstractNode {
  public BranchNode(String name) {
  super(name);
  }
  private Collection<Node> childs = new ArrayList<Node>();
  public void addNode(Node node) {
  childs.add(node);
  }
  public Iterator<Node> iterator() {
  return childs.iterator();
  }
  }
  树枝节点可以添加子节点,叶子节点或者另外一个树枝节点。但是我们注意到,这里的迭代器只是简单地返回了该节点直属子节点集合的迭代器。什么意思呢?这意味着如果你通过这个迭代器去遍历这个节点,你能获得是只是所有直接添加到这个节点上的子节点。要想递归地遍历子节点的子节点,以及子节点的子节点的子节点……我们就需要一个特殊的迭代器。
  用一个深度优先遍历的迭代器怎么样?所谓深度优先,通俗的讲就是沿着一条路走下去,直到走不通为止,再回过头来看看有没有别的路可走。
  public class DepthFirstIterator implements Iterator<Node> {
  private Stack<Iterator<Node>> stack = new Stack<Iterator<Node>>();
  public DepthFirstIterator(Iterator<Node> it) {
  stack.push(it);
  }
  public boolean hasNext() {
  if (stack.isEmpty()) {
  return false;
  } else {
  Iterator<Node> it = stack.peek();
  if (it.hasNext()) {
  return true;
  } else {
  stack.pop();
  return hasNext();
  }
  }
  }
  public Node next() {
  if (hasNext()) {
  Iterator<Node> it = stack.peek();
  Node next = it.next();
  if (next instanceof BranchNode) {
  stack.push(next.iterator());
  }
  return next;
  } else {
  return null;
  }
  }
  public void remove() {
  throw new UnsupportedOperationException("Can’t remove node, yet");
  }
  }
  代码不是很长,理解起来可比较费劲。这不仅是因为我很懒没有写注释,更是因为有可恶的递归在。先从构造函数看起,一个包含了迭代器的构造函数,也就是说,这是个迭代器的迭代器……这该怎么理解呢?可以把它理解成树枝节点迭代器的一个改良的迭代器。树枝节点的迭代器只知道如何找到第一级子节点,而这个迭代器则可以沿着子节点一直寻找下去,直到找到叶子节点,然后再返回来继续寻找。好吧,说起来简单,怎么做呢?
  先看如何找到“下一个”节点吧,看next()方法:
  如果存在下一个节点,那么开始下一个节点的寻找之旅。否则返回null结束。
  首先,通过树枝节点自带的迭代器,找到树枝节点的第一个子节点,这个子节点就是我们要找的“第一个”节点。这很简单,对吧?那么下一个节点是哪一个?这取决于我们找到的第一个节点是什么类型,如果是叶子节点,那么很简单,下一个节点跟树枝节点迭代器定义的下一个节点一样,也就是树枝节点的第二个直属子节点。如果是树枝节点呢?这个时候它将被当成一个新的起点,被压入堆栈,下一次遍历将从这个节点开始重复上面的逻辑,也就是递归。听起来并不复杂,不是吗?

[1] [2] 下一页

责任编辑:小草

文章搜索:
 相关文章
热点资讯
资讯快报
热门课程培训