Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Algorithms (2006)

.pdf
Скачиваний:
253
Добавлен:
17.08.2013
Размер:
9.67 Mб
Скачать

Chapter 10

assertTrue(_l.isLarger()); assertTrue(_m.isLarger()); assertTrue(_p.isLarger());

}

Finally, you create some tests for equals(). The equals() method will be very important when it comes time to test the BinarySearchTree class, as it enables you to compare the structure produced when inserting and deleting nodes with the expected result. The implementation will start from the current node and compare the values as well as the left and right children all the way down to the leaf nodes.

In testEquals(), you construct a replica of the node structure. You then compare each of the instance variables with their local variable counterparts, as well as check some boundary conditions just to make sure you haven’t hard-coded equals() to always return true!

public void testEquals() { Node a = new Node(“A”); Node h = new Node(“H”); Node k = new Node(“K”); Node p = new Node(“P”);

Node f = new Node(“F”, null, h); Node m = new Node(“M”, null, p); Node d = new Node(“D”, a, f); Node l = new Node(“L”, k, m); Node i = new Node(“I”, d, l);

assertEquals(a, _a); assertEquals(d, _d); assertEquals(f, _f); assertEquals(h, _h); assertEquals(i, _i); assertEquals(k, _k); assertEquals(l, _l); assertEquals(m, _m); assertEquals(p, _p);

assertFalse(_i.equals(null)); assertFalse(_f.equals(_d));

}

Now that you have the tests in place, you can create the node class itself in the next Try it Out section.

Try It Out

Implementing a Node Class

Create the node class as follows:

package com.wrox.algorithms.bstrees;

public class Node implements Cloneable { private Object _value;

private Node _parent; private Node _smaller; private Node _larger;

244

Binary Search Trees

public Node(Object value) { this(value, null, null);

}

public Node(Object value, Node smaller, Node larger) { setValue(value);

setSmaller(smaller);

setLarger(larger);

if (smaller != null) { smaller.setParent(this);

}

if (larger != null) { larger.setParent(this);

}

}

public Object getValue() { return _value;

}

public void setValue(Object value) {

assert value != null : “value can’t be null”; _value = value;

}

public Node getParent() { return _parent;

}

public void setParent(Node parent) { _parent = parent;

}

public Node getSmaller() { return _smaller;

}

public void setSmaller(Node smaller) {

assert smaller != getLarger() : “smaller can’t be the same as larger”; _smaller = smaller;

}

public Node getLarger() { return _larger;

}

public void setLarger(Node larger) {

assert larger != getSmaller() : “larger can’t be the same as smaller”; _larger = larger;

}

245

Chapter 10

public boolean isSmaller() {

return getParent() != null && this == getParent().getSmaller();

}

public boolean isLarger() {

return getParent() != null && this == getParent().getLarger();

}

public Node minimum() { Node node = this;

while (node.getSmaller() != null) { node = node.getSmaller();

}

return node;

}

public Node maximum() { Node node = this;

while (node.getLarger() != null) { node = node.getLarger();

}

return node;

}

public Node successor() {

if (getLarger() != null) { return getLarger().minimum();

}

Node node = this;

while (node.isLarger()) { node = node.getParent();

}

return node.getParent();

}

public Node predecessor() {

if (getSmaller() != null) { return getSmaller().maximum();

}

Node node = this;

while (node.isSmaller()) { node = node.getParent();

}

246

Binary Search Trees

return node.getParent();

}

public int size() { return size(this);

}

public boolean equals(Object object) { if (this == object) {

return true;

}

if (object == null || object.getClass() != getClass()) { return false;

}

Node other = (Node) object;

return getValue().equals(other.getValue())

&&equalsSmaller(other.getSmaller())

&&equalsLarger(other.getLarger());

}

private int size(Node node) { if (node == null) {

return 0;

}

return 1 + size(node.getSmaller()) + size(node.getLarger());

}

private boolean equalsSmaller(Node other) { return getSmaller() == null && other == null

|| getSmaller() != null && getSmaller().equals(other);

}

private boolean equalsLarger(Node other) { return getLarger() == null && other == null

|| getLarger() != null && getLarger().equals(other);

}

}

How It Works

Each node holds a value, a reference to a parent, a smaller (or left) child, and a larger (or right) child:

package com.wrox.algorithms.bstrees;

public class Node { private Object _value; private Node _parent; private Node _smaller; private Node _larger;

...

}

247

Chapter 10

You’ve also provided two constructors. The first constructor is for creating leaf nodes — those with no children — so its only argument is a value:

public Node(Object value) { this(value, null, null);

}

The second constructor, however, is somewhat of a convenience, enabling you to create nodes that have children. Notice that if you specify a non-null child, the constructor conveniently sets that child’s parent. This, as you may recall from the tests, makes it trivial to wire nodes together into a tree structure:

public Node(Object value, Node smaller, Node larger) { setValue(value);

setSmaller(smaller);

setLarger(larger);

if (smaller != null) { smaller.setParent(this);

}

if (larger != null) { larger.setParent(this);

}

}

Once constructed, you need access to the node’s value, its parent, and any of its children. For this, you create some standard getters and setters. Nothing too strange there except that you’ve put in a few extra assertions — for example, checking to make sure that you haven’t set both children to the same node:

public Object getValue() { return _value;

}

public void setValue(Object value) {

assert value != null : “value can’t be null”; _value = value;

}

public Node getParent() { return _parent;

}

public void setParent(Node parent) { _parent = parent;

}

public Node getSmaller() { return _smaller;

}

public void setSmaller(Node smaller) {

assert smaller != getLarger() : “smaller can’t be the same as larger”; _smaller = smaller;

}

248

Binary Search Trees

public Node getLarger() { return _larger;

}

public void setLarger(Node larger) {

assert larger != getSmaller() : “larger can’t be the same as smaller”; _larger = larger;

}

Next, follow some convenience methods for determining various characteristics of each node.

The methods isSmaller() and isLarger() return true only if the node is the smaller or larger child of its parent, respectively:

public boolean isSmaller() {

return !isRoot() && this == getParent().getSmaller();

}

public boolean isLarger() {

return !isRoot() && this == getParent().getLarger();

}

Finding the minimum or maximum is not much more complex. Recall that the minimum of a node is its smallest child, and the maximum is its largest (or itself if it has no children). Notice that the code for maximum() is almost identical to that of minimum(); whereas minimum() calls getSmaller(), maximum() calls getLarger():

public Node minimum() { Node node = this;

while (node.getSmaller() != null) { node = node.getSmaller();

}

return node;

}

public Node maximum() { Node node = this;

while (node.getLarger() != null) { node = node.getLarger();

}

return node;

}

Finding the successor and predecessor of a node is a little bit more involved. Recall that the successor of a node is either the minimum of its largest child — if there is one — or the first node you encounter after a “right-hand” turn while moving up the tree.

249

Chapter 10

Looking at successor(), you can see that if the node has a larger child, then you take its minimum. If not, you start moving up the tree looking for the “right-hand” turn by checking whether the current node is the larger of its parent’s children. If it is the larger, then it must be to the right of its parent, and you would be moving to the left back up the tree. In essence, you are moving back up the tree looking for the first node that is the smaller (that is, left) child of its parent. Once found, you know you would then be making a “right-hand” turn to get to the parent — precisely what you were looking for.

Also notice that, as was the case with minimum() and maximum(), successor() and predecessor() are mirror images of each other: where successor() takes the minimum, predecessor() takes the maximum; when successor calls isLarger(), predecessor calls isSmaller():

public Node successor() {

if (getLarger() != null) { return getLarger().minimum();

}

Node node = this;

while (node.isLarger()) { node = node.getParent();

}

return node.getParent();

}

public Node predecessor() {

if (getSmaller() != null) { return getSmaller().maximum();

}

Node node = this;

while (node.isSmaller()) { node = node.getParent();

}

return node.getParent();

}

Finally, we have equals(). This method is a node’s most complex (though still fairly straightforward), but it will be used extensively later to check the structure of the trees created by the BinarySearchTree class.

Besides the boilerplate code, the public equals() method compares three aspects of each node for equality: the value, the smaller child, and the larger child. Comparing values is simple: You know the value can never be null, so simply delegating to the value’s equals() method is sufficient:

public boolean equals(Object object) { if (this == object) {

return true;

}

250

Binary Search Trees

if (object == null || object.getClass() != getClass()) { return false;

}

Node other = (Node) object;

return getValue().equals(other.getValue())

&&equalsSmaller(other.getSmaller())

&&equalsLarger(other.getLarger());

}

Comparing child nodes is a little more involved because not only can either or both of the children be null, you must also check the children’s children and their children, and so on, all the way to the leaf nodes. For this, you created two helper methods: equalsSmaller() and equalsLarger().These methods compare the children of the current node with the corresponding child of the other node. For example, equalsSmaller() compares the current node’s smaller child with the smaller child of the other node. If both children are null, the nodes are considered equal. If only one child is null, they can’t possibly be equal. If, however, both the current node and the other node have a smaller child, then you recursively call equals() to continue checking down the tree:

private boolean equalsSmaller(Node other) { return getSmaller() == null && other == null

|| getSmaller() != null && getSmaller().equals(other);

}

private boolean equalsLarger(Node other) { return getLarger() == null && other == null

|| getLarger() != null && getLarger().equals(other);

}

That’s it for the node class. In the next Try it Out section, you create some tests in preparation for your final binary search tree implementation.

Try It Out

Testing a Binary Search Tree

Create the test class as follows:

package com.wrox.algorithms.bstrees;

import com.wrox.algorithms.sorting.NaturalComparator; import junit.framework.TestCase;

public class BinarySearchTreeTest extends TestCase { private Node _a;

private Node _d; private Node _f; private Node _h; private Node _i; private Node _k; private Node _l; private Node _m; private Node _p;

251

Chapter 10

private Node _root;

private BinarySearchTree _tree;

protected void setUp() throws Exception { super.setUp();

_a = new Node(“A”); _h = new Node(“H”); _k = new Node(“K”); _p = new Node(“P”);

_f = new Node(“F”, null, _h); _m = new Node(“M”, null, _p); _d = new Node(“D”, _a, _f); _l = new Node(“L”, _k, _m); _i = new Node(“I”, _d, _l); _root = _i;

_tree = new BinarySearchTree(NaturalComparator.INSTANCE); _tree.insert(_i.getValue()); _tree.insert(_d.getValue()); _tree.insert(_l.getValue()); _tree.insert(_a.getValue()); _tree.insert(_f.getValue()); _tree.insert(_k.getValue()); _tree.insert(_m.getValue()); _tree.insert(_h.getValue()); _tree.insert(_p.getValue());

}

public void testInsert() { assertEquals(_root, _tree.getRoot());

}

public void testSearch() {

assertEquals(_a, _tree.search(_a.getValue())); assertEquals(_d, _tree.search(_d.getValue())); assertEquals(_f, _tree.search(_f.getValue())); assertEquals(_h, _tree.search(_h.getValue())); assertEquals(_i, _tree.search(_i.getValue())); assertEquals(_k, _tree.search(_k.getValue())); assertEquals(_l, _tree.search(_l.getValue())); assertEquals(_m, _tree.search(_m.getValue())); assertEquals(_p, _tree.search(_p.getValue()));

assertNull(_tree.search(“UNKNOWN”));

}

public void testDeleteLeafNode() {

Node deleted = _tree.delete(_h.getValue()); assertNotNull(deleted); assertEquals(_h.getValue(), deleted.getValue());

_f.setLarger(null); assertEquals(_root, _tree.getRoot());

}

252

Binary Search Trees

public void testDeleteNodeWithOneChild() { Node deleted = _tree.delete(_m.getValue()); assertNotNull(deleted);

assertEquals(_m.getValue(), deleted.getValue());

_l.setLarger(_p); assertEquals(_root, _tree.getRoot());

}

public void testDeleteNodeWithTwoChildren() { Node deleted = _tree.delete(_i.getValue()); assertNotNull(deleted);

assertEquals(_i.getValue(), deleted.getValue());

_i.setValue(_k.getValue()); _l.setSmaller(null); assertEquals(_root, _tree.getRoot());

}

public void testDeleteRootNodeUntilTreeIsEmpty() { while (_tree.getRoot() != null) {

Object key = _tree.getRoot().getValue(); Node deleted = _tree.delete(key); assertNotNull(deleted); assertEquals(key, deleted.getValue());

}

}

}

How It Works

All of our tests use the BinarySearchTree class to manipulate a tree so that it looks like the one shown in Figure 10-1. Then, as you did with your node tests, you compare this tree with one you have handcrafted. If they match, then you know your code works as expected.

The BinarySearchTreeTest class defines some nodes for comparison and constructs a BinarySearchTree with the same values as the nodes. Notice that you have inserted the values in a very specified, yet non-alphabetical, order. Remember that your tree performs no balancing. If you were to insert the values in alphabetical order, you would end up with a degenerate tree — one that looks like a linked list (refer to Figure 10-7). Instead, you insert the values in an order specifically designed to produce a balanced tree that looks like the one in Figure 10-1. Can you see why this works? You’ve inserted the values pre-order. That is, the order of insertion is such that the parent node of each subtree is added to the tree before either of its children:

package com.wrox.algorithms.bstrees;

import com.wrox.algorithms.sorting.NaturalComparator; import junit.framework.TestCase;

public class BinarySearchTreeTest extends TestCase { private Node _a;

private Node _d;

253