提交 7ab86d9c 编写于 作者: W wizardforcel

ch6

上级 2f1cd3b0
# 第六章 树的遍历
> 原文:[Chapter 6 Tree traversal](http://greenteapress.com/thinkdast/html/thinkdast007.html)
> 译者:[飞龙](https://github.com/wizardforcel)
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
本章将介绍一个 Web 搜索引擎,我们将在本书其余部分开发它。我描述了搜索引擎的元素,并介绍了第一个应用程序,一个从维基百科下载和解析页面的 Web 爬行器。本章还介绍了深度优先搜索的递归实现,以及迭代实现,它使用 Java`Deque`实现“后入先出”的栈。
## 6.1 搜索引擎
......@@ -146,4 +154,80 @@ private static void recursiveDFS(Node node) {
在这个例子中,我们在遍历子节点之前打印每个`TextNode`的内容,所以这是一个“前序”遍历的例子。你可以在 <http://thinkdast.com/treetrav> 上了解“前序”,“后序”和“中序”遍历。对于此应用程序,遍历顺序并不重要。
通过进行递归调用,`recursiveDFS`使用调用堆栈(<http://thinkdast.com/callstack>)来跟踪子节点并以正确的顺序处理它们。作为替代,我们可以使用栈数据结构自己跟踪节点;如果我们这样做,我们可以避免递归并迭代遍历树。
通过进行递归调用,`recursiveDFS`使用调用栈(<http://thinkdast.com/callstack>)来跟踪子节点并以正确的顺序处理它们。作为替代,我们可以使用栈数据结构自己跟踪节点;如果我们这样做,我们可以避免递归并迭代遍历树。
## 6.6 Java 中的栈
在我解释 DFS 的迭代版本之前,我将解释栈数据结构。我们将从栈的一般概念开始,我将使用小写`s`指代“栈”。然后我们将讨论两个 Java`interfaces`,它们定义了栈的方法:`Stack``Deque`
栈是与列表类似的数据结构:它是维护元素顺序的集合。栈和列表之间的主要区别是栈提供的方法较少。在通常的惯例中,它提供:
`push`:它将一个元素添加到栈顶。
`pop`:它从栈中删除并返回最顶部的元素。
`peek`:它返回最顶部的元素而不修改栈。
`isEmpty`:表示栈是否为空。
因为`pop`总是返回最顶部的元素,栈也称为 LIFO,代表“后入先出”。栈的替代品是“队列”,它返回的元素顺序和添加顺序相同;即“先入先出(FIFO)。
为什么栈和队列是有用的,可能不是很明显:它们不提供任何列表没有的功能;实际上它们提供的功能更少。那么为什么不使用列表的一切?有两个原因:
+ 如果你将自己限制于一小部分方法 - 也就是小型 API - 你的代码将更加易读,更不容易出错。例如,如果使用列表来表示栈,则可能会以错误的顺序删除元素。使用栈 API,这种错误在字面上是不可能的。避免错误的最佳方法是使它们不可能。
+ 如果一个数据结构提供了小型 API,那么它更容易实现。例如,实现栈的简单方法是单链表。当我们压入一个元素时,我们将它添加到列表的开头;当我们弹出一个元素时,我们在开头删除它。对于链表,在开头添加和删除是常数时间的操作,因此这个实现是高效的。相反,大型 API 更难实现高效。
为了在 Java 中实现栈,你有三个选项:
+ 继续使用`ArrayList``LinkedList`。如果使用`ArrayList`,请务必从最后添加和删​​除,这是一个常数时间的操作。并且小心不要在错误的地方添加元素,或以错误的顺序删除它们。
+ Java 提供了一个`Stack`类,它提供了一组标准的栈方法。但是这个类是 Java 的一个旧部分:它与 Java 集合框架不兼容,后者之后才出现。
+ 最好的选择可能是使用`Deque`接口的一个实现,如`ArrayDeque`
`Deque`代表“双向队列”;它应该被发音为“deck”,但有些人叫它“deek”。在 Java 中, `Deque`接口提供`push``pop``peek``isEmpty`,因此你可以将`Deque`用作栈。它提供了其他方法,你可以阅读 <http://thinkdast.com/deque>,但现在我们不会使用它们。
## 6.7 迭代式 DFS
这里是 DFS 的迭代版本,它使用`ArrayDeque`来表示`Node`对象的栈。
```java
private static void iterativeDFS(Node root) {
Deque<Node> stack = new ArrayDeque<Node>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
if (node instanceof TextNode) {
System.out.print(node);
}
List<Node> nodes = new ArrayList<Node>(node.childNodes());
Collections.reverse(nodes);
for (Node child: nodes) {
stack.push(child);
}
}
}
```
参数`root`是我们想要遍历的树的根节点,所以我们首先创建栈并将根节点压入它。
循环持续到栈为空。每次迭代,它会从栈中弹出`Node`。如果它得到`TextNode`,它打印内容。然后它把子节点们压栈。为了以正确的顺序处理子节点,我们必须以相反的顺序将它们压栈; 我们通过将子节点复制成一个`ArrayList`,原地反转元素,然后遍历反转的`ArrayList`
DFS 的迭代版本的一个优点是,更容易实现为 Java`Iterator`;你会在下一章看到如何实现。
但是首先,有一个`Deque`接口的最后的注意事项:除了`ArrayDeque`,Java 提供另一个`Deque`的实现,我们的老朋友`LinkedList``LinkedList`实现两个接口,`List``Deque`(还有`Queue`)。你得到哪个接口,取决于你如何使用它。例如,如果将`LinkedList`对象赋给`Deque`变量,如下所示:
```java
Deqeue<Node> deque = new LinkedList<Node>();
```
你可以使用`Deque`接口中的方法,但不是所有`List`中的方法。如果你将其赋给`List`变量,像这样:
```java
List<Node> deque = new LinkedList<Node>();
```
你可以使用`List`接口中的方法,但不是所有`Deque`中的方法。并且如果像这样赋值:
```java
LinkedList<Node> deque = new LinkedList<Node>();
```
你可以使用所有方法,但是混合了来自不同接口的方法。你的代码会更不可读,并且更易于出错。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册