提交 75af26f7 编写于 作者: W wizardforcel

ch13.

上级 daa25695
......@@ -207,11 +207,8 @@ public class MyTreeMap<K, V> implements Map<K, V> {
你的`put`实现的是时间应该与树的高度`h`成正比,而不是元素的数量`n`。理想情况下,你只需搜索一次树,但如果你发现两次更容易搜索,可以这样做:它会慢一些,但不会改变增长级别。
> 译者注:如果你同时跟踪和返回父节点,可以只搜索一次。
最后,你应该填充`keySet`。根据 <http://thinkdast.com/mapkeyset> 的文档,该方法应该返回一个`Set`,可以按顺序迭代键;也就是说,按照`compareTo`方法,升序迭代。我们在 8.3 节中使用的`HashSet`实现不会维护键的顺序,但`LinkedHashSet`实现可以。你可以阅读 <http://thinkdast.com/linkedhashset>
> 译者注:你也可以使用`TreeSet`,它和你正在实现的东西原理差不多,只不过把 BST 换成了红黑树。
我提供了一个`keySet`的大纲,创建并返回`LinkedHashSet`
......
# 第十三章 二叉搜索树
本章介绍了上一个练习的解决方案,然后测试树形映射的性能。我展示了一个实现的问题,并解释了 Java 的`TreeMap`如何解决它。
## 13.1 简单的`MyTreeMap`
上一个练习中,我给了你`MyTreeMap`的大纲,并让你填充缺失的方法。现在我会展示结果,从`findNode`开始:
```java
private Node findNode(Object target) {
// some implementations can handle null as a key, but not this one
if (target == null) {
throw new IllegalArgumentException();
}
// something to make the compiler happy
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) target;
// the actual search
Node node = root;
while (node != null) {
int cmp = k.compareTo(node.key);
if (cmp < 0)
node = node.left;
else if (cmp > 0)
node = node.right;
else
return node;
}
return null;
}
```
`findNode``containsKey``get`所使用的一个私有方法;它不是`Map`接口的一部分。参数`target`是我们要查找的键。我在上一个练习中解释了这种方法的第一部分:
+ 在这个实现中,`null`不是键的合法值。
+ 在我们可以在`target`上调用`compareTo`之前,我们必须把它强制转换为某种形式的`Comparable`。这里使用的“类型通配符”会尽可能允许;也就是说,它适用于任何实现`Comparable`类型,并且它的`compareTo`接受`K`或者任和`K`的超类。
之后,实际搜索比较简单。我们初始化一个循环变量`node`来引用根节点。每次循环中,我们将目标与`node.key`比较。如果目标小于当前键,我们移动到左子树。如果它更大,我们移动到右子树。如果相等,我们返回当前节点。
如果在没有找到目标的情况下,我们到达树的底部,我就认为,它不在树中并返回`null`
## 13.2 搜索值
我在前面的练习中解释了,`findNode`运行时间与树的高度成正比,而不是节点的数量,因为我们不必搜索整个树。但是对于`containsValue`,我们必须搜索值,而不是键;BST 的特性不适用于值,因此我们必须搜索整个树。
我的解法是递归的:
```java
public boolean containsValue(Object target) {
return containsValueHelper(root, target);
}
private boolean containsValueHelper(Node node, Object target) {
if (node == null) {
return false;
}
if (equals(target, node.value)) {
return true;
}
if (containsValueHelper(node.left, target)) {
return true;
}
if (containsValueHelper(node.right, target)) {
return true;
}
return false;
}
```
`containsValue`将目标值作为参数,并立即调用`containsValueHelper`,传递树的根节点作为附加参数。
这是`containsValueHelper`的工作原理:
+ 第一个`if`语句检查递归的边界情况。如果`node``null`,那意味着我们已经递归到树的底部,没有找到`target`,所以我们应该返回`false`。请注意,这只意味着目标没有出现在树的一条路径上;它仍然可能会在另一条路径上被发现。
+ 第二种情况检查我们是否找到了我们正在寻找的东西。如果是这样,我们返回`true`。否则,我们必须继续。
+ 第三种情况是执行递归调用,在左子树中搜索`target`。如果我们找到它,我们可以立即返回`true`,而不搜索右子树。否则我们继续。
+ 第四种情况是搜索右子树。同样,如果我们找到我们正在寻找的东西,我们返回`true`。否则,我们搜索完了整棵树,返回`false`
该方法“访问”了树中的每个节点,所以它的所需时间与节点数成正比。
## 13.3 实现`put`
`put`方法比起`get`要复杂一些,因为要处理两种情况:(1)如果给定的键已经在树中,则替换并返回旧值;(2)否则必须在树中添加一个新的节点,在正确的地方。
在上一个练习中,我提供了这个起始代码:
```java
public V put(K key, V value) {
if (key == null) {
throw new IllegalArgumentException();
}
if (root == null) {
root = new Node(key, value);
size++;
return null;
}
return putHelper(root, key, value);
}
```
并且让你填充`putHelper`。这里是我的答案:
```java
private V putHelper(Node node, K key, V value) {
Comparable<? super K> k = (Comparable<? super K>) key;
int cmp = k.compareTo(node.key);
if (cmp < 0) {
if (node.left == null) {
node.left = new Node(key, value);
size++;
return null;
} else {
return putHelper(node.left, key, value);
}
}
if (cmp > 0) {
if (node.right == null) {
node.right = new Node(key, value);
size++;
return null;
} else {
return putHelper(node.right, key, value);
}
}
V oldValue = node.value;
node.value = value;
return oldValue;
}
```
第一个参数`node`最初是树的根,但是每次我们执行递归调用,它指向了不同的子树。就像`get`一样,我们用`compareTo`方法来弄清楚,跟随哪一条树的路径。如果`cmp < 0`,我们添加的键小于`node.key`,那么我们要走左子树。有两种情况:
+ 如果左子树为空,那就是,如果`node.left``null`,我们已经到达树的底部而没有找到`key`。这个时候,我们知道`key`不在树上,我们知道它应该放在哪里。所以我们创建一个新节点,并将它添加为`node`的左子树。
+ 否则我们进行递归调用来搜索左子树。
如果`cmp > 0`,我们添加的键大于`node.key`,那么我们要走右子树。我们处理的两个案例与上一个分支相同。最后,如果`cmp == 0`,我们在树中找到了键,那么我们更改它并返回旧的值。
我使用递归编写了这个方法,使它更易于阅读,但它可以直接用迭代重写一遍,你可能想留作练习。
## 13.4 中序遍历
我要求你编写的最后一个方法是`keySet`,它返回一个`Set`,按升序包含树中的键。在其他`Map`实现中,`keySet`返回的键没有特定的顺序,但是树形实现的一个功能是,对键进行简单而有效的排序。所以我们应该利用它。
这是我的答案:
```java
public Set<K> keySet() {
Set<K> set = new LinkedHashSet<K>();
addInOrder(root, set);
return set;
}
private void addInOrder(Node node, Set<K> set) {
if (node == null) return;
addInOrder(node.left, set);
set.add(node.key);
addInOrder(node.right, set);
}
```
`keySet`中,我们创建一个`LinkedHashSet`,这是一个`Set`实现,使元素保持有序(与大多数其他`Set`实现不同)。然后我们调用`addInOrder`来遍历树。
第一个参数`node`最初是树的根,但正如你的期望,我们用它来递归地遍历树。`addInOrder`对树执行经典的“中序遍历”。
如果`node``null`,这意味着子树是空的,所以我们返回,而不向`set`添加任何东西。否则我们:
1. 按顺序遍历左子树。
1. 添加`node.key`
1. 按顺序遍历右子树。
请记住,BST 的特性保证左子树中的所有节点都小于`node.key`,并且右子树中的所有节点都更大。所以我们知道,`node.key`已按正确的顺序添加。
递归地应用相同的参数,我们知道左子树中的元素是有序的,右子树中的元素也一样。并且边界情况是正确的:如果子树为空,则不添加任何键。所以我们可以认为,该方法以正确的顺序添加所有键。
因为`containsValue`方法访问树中的每个节点,所以所需时间与`n`成正比。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册