The Node objects used in mutable trees contain the usual methods plus these new ones:
public void setValue(Object v) public void setLeft(MutableTree t) public void setRight(MutableTree t)Of course, these are used to reset a node's value and links to left and right subtrees.
Important: When using mutable trees, you cannot share nodes or leaves! If a node is shared by two distinct trees, and you alter the node's value or link, this affects both of the trees that share the node. The outcome is almost always wrong.
You can implement an application with mutable trees if both of the following hold true:
/** parent returns (the address of) this tree's parent node */ public Node parent() /** setParent tells this tree to remember that it has a new_parent */ public void setParent(Node new_parent)Here is a picture of a mutable tree, rooted at address a1, that holds the values, a, b, and c along with parent links:
a1: Node value = "a" parent = null left = a2 right = a3 a2: Node a3: Node value = "b" value = "c" parent = a1 parent = a1 left = a4 left = a6 right = a5 right = a7 a4: Leaf a6: Leaf parent = a2 parent = a3 a5: Leaf a7: Leaf parent = a2 parent = a3We might draw a terse picture of these objects as
a1: a /^ ^\ v | | v a2: b a3: c /^ ^\ /^ ^\ v | | v v | | v a4 a5 a6 a7where the down-pointing arrows denote the left- and right-subtree links, and the up-pointing arrows denote the parent links.
Here is a quick summary of class MutableTree:
/** MutableTree defines the data type of binary trees that can be altered * once they are constructed. Important: substructures of this form * of tree _cannot_ be shared! * Forms of this tree are intended to be: * (i) a leaf, which holds the address of its parent node (if any), or * (ii) a node that holds a value, a left subtree, a right subtree, * and the address of its parent node (if any). */ public abstract class MutableTree { // accessor methods: public Object value() public MutableTree left() public MutableTree right() public Node parent() // mutator methods: public void setValue(Object v) public void setLeft(MutableTree t) public void setRight(MutableTree t) public void setParent(Node p) }The coding of a leaf looks like this:
/** Leaf models a tree leaf---an empty tree */ public class Leaf extends MutableTree { private Node my_parent; /** Constructor Leaf constructs the empty tree * @param p - this node's parent */ public Leaf(Node p) { my_parent = p; } ... }and a node looks like this:
/** Node models a nonempty tree node, holding a value, two subtrees, and * a link to this Node's parent Node */ public class Node extends MutableTree { private Object val; private MutableTree left; private MutableTree right; private Node my_parent; /** Constructor Node constructs the tree node * @param p - this node's parent node * @param v - the value held in the node * @param l - the left subtree * @param r - the right subtree */ public Node(Node p, Object v, MutableTree l, MutableTree r) { my_parent = p; val = v; left = l; right = r; } ... }
Here are some basic actions on mutable trees. First, lets build a Node that holds "b" and two leaves:
Leaf empty1 = new Leaf(null); Leaf empty2 = new Leaf(null); Node btree = new Node(null, "b", empty1, empty2); empty1.setParent(btree); // connect leaves to parent node empty2.setParent(btree)Notice that the parent links in the leaves must be set.
The above steps are tedious and are best placed in a helper method:
/** makeNewNode builds a Node that holds value and links to parent */ public Node makeNewNode(Node parent, Object value) { Leaf empty1 = new Leaf(null); Leaf empty2 = new Leaf(null); Node new_node = new Node(parent, value, empty1, empty2); empty1.setParent(new_node); // connect leaves to parent node empty2.setParent(new_node); return new_node; }Next, let's make another Node, holding "c", and then connect the two nodes to yet another that holds "a":
Node ctree = makeNewNode(null, "c"); // build a tree that holds just "c" Node atree = new Node(null, "a", btree, ctree); btree.setParent(atree); ctree.setParent(atree);
Although setting the parent links is a bother, the links pay handsomely when we must delete a node from the tree. Say that we must delete btree, replacing it by a leaf. We can do this without traversing the tree to find btree's position:
Node bparent = btree.parent(); if ( bparent != null ) // verify that btree is not the root { Leaf replacement = new Leaf(bparent); if ( bparent.left() == btree ) // is btree its parent's left subtree? { bparent.setLeft(replacement); } else // it's the right subtree of its parent: { bparent.setRight(replacement); } }
You can find the coding of the classes for mutable trees at
private MutableTree the_ordered_tree; ... the_ordered_tree = insert(u, the_ordered_tree);where the insert method is defined as follows:
/** insert places u into its proper position in tree t, * and returns the address of the altered tree. (This is usually just * t itself, unless t is a Leaf, in which case it must be replaced * by a new Node.) */ public MutableTree insert(Record u, MutableTree t) { MutableTree answer; if ( t instanceof Leaf ) { answer = makeNewNode(null, u); } // see makeNewNode above else // t is a Node: { Record v = t.value(); if ( u.getKey() < v.getKey() ) // remember to properly recode < { MutableTree new_left = insert(u, t.left()); // attach the revised left subtree to t: new_left.setParent(t); t.setLeft(new_left); } else // u.getKey() >= v.getKey() : { MutableTree new_right = insert(u, t.right()); new_right.setParent(t); t.setRight(new_right); } answer = t; } return answer; }The method asks the usual questions to decide whether to attach into the left subtree or the right one. When a Leaf is finally found at the correct position for attachment, the Leaf is replaced by a new Node that holds u. Notice that we construct only one new node---the node that holds u---and we reset only one link, the link to u's parent. We do not reassemble the tree as we did when we worked with immutable trees; the updating of the_ordered_tree is done ``in place.''
With a bit of effort, you can rewrite insert so that it uses a loop to traverse the path from the root to the Leaf that must be replaced by the new Node and its value, u. (Review this technique from the previous lecture.)
A package, MutableOrderedTree, has been written for constructing mutable ordered trees. The package contains methods for insertion, finding, and deletion. You can find it here:
Say that tree t has the root node, N, a left subtree, L, and a right subtree, R. There are two techniques for deleting N: the first is simple but has a bad practical behavior:
N L / \ / \ L R ==> ---R / \ / \ / \ ---. --- ---The dot in the first drawing represents the rightmost leaf; this leaf is replaced by tree R by resetting a link. The resulting tree is ordered.
This alteration is easy to do, but it makes the ordered tree highly unbalanced very quickly---searches into the R-part are dramatically slowed.
The second approach is more complex but actually helps to rebalance the ordered tree. We move an innermost node to the root. Here is a picture first, which shows how innermost node, W, becomes the new root:
N W / \ / \ L R ==> L R / \ / \ / \ / \ ---Z --- ---Z --- / \ / \ Y W Y X /\ / \ /\ /\ X . /\The dot is a leaf; the letters denote nodes. W is the rightmost node in subtree L; since W is rightmost, its right subtree must be a leaf. We make W the new root and leave behind W's left subtree, X, attaching it to W's former parent, Z. This makes the new tree ordered.
In the above situation, we could also choose to move the leftmost node in subtree R to be the root; this works equally well.
Here is an algorithm for making the innermost rightmost node of L into the root:
You can see the implementation of this form of deletion in class OrderedTree in package MutableOrderedTree.
Finally, there are more sophisticated methods for inserting and deleting values in ordered trees. So-called AVL trees rearrange their subtrees after every insertion and after every deletion to ensure that the tree is always almost balanced. We will study AVL trees later in the course.