Mutable trees, parent links, and node deletions

A mutable tree is a tree in which you can change the values and subtrees of its nodes. Let MutableTree be the data-type name of mutable binary trees.

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.

When to use mutable trees

The main motivation for using mutable trees is to avoid construction of lots of new Nodes when inserting and deleting elements. In particular, Node deletion becomes far simpler.

You can implement an application with mutable trees if both of the following hold true:

  1. The application maintains only one tree, and there is never a need to make copies of the tree, to ``undo'' changes to the tree, to ``backtrack'' to an earlier version, nor to share nodes/leaves/subtrees.
  2. The values in the tree's nodes must be frequently changed or nodes are frequently deleted or subtrees are frequently rearranged.
If you are uncertain if both (1) and (2) hold, then play it safe and use immutable trees.

Mutable trees have parents

Because mutable trees are used when there are frequent changes, the frequent changes should be done relatively efficiently. When a node must be moved, it is valuable for a node to hold the address of its parent node, that is, the node that links to the node that must be moved---this lets us change the parent's link, too. For this reason, we will add an extra attribute to every leaf and node---the address of its parent. This motivates two more methods for both leaves and nodes:
/** 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 = a3
We might draw a terse picture of these objects as
 
         a1: a
       /^    ^\  
      v |    | v 
    a2: b    a3: c
    /^  ^\    /^  ^\
   v |  | v  v |  | v
   a4   a5    a6   a7
where 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

package MutableTree


Methods for mutable trees

Let's start with a standard example---inserting a value, u, into an ordered tree. Say there is a variable, named the_ordered_tree, that holds (the address of) the tree to be altered. Here is a simple strategy, based on the technique we used with immutable trees:
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:

package MutableOrderedTree


Deletion of a node from a mutable tree

Deletion of nodes from an immutable tree is painful and should be avoided if possible. If you must do deletions, then try to work with a mutable, ordered tree. To do a deletion, tackle the problem in two steps:
  1. Find the node that holds the value to be deleted. The node you locate is at the root of some subtree, t, embedded within the overall tree.
  2. Given tree t, remove its root node and return the address of the tree you reassemble with t's left and right subtrees. Attach this address to t's parent.
We already know how to find values in a tree, so we take Step (1) as solved. What remains is writing an algorithm for deleting the root of a (sub)tree that is mutable and ordered, building an ordered tree from what's left, and returning the address of the rebuilt tree. (The address is then linked to the parent.)

Naive deletion algorithm

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:

Use L as if it is the new tree. Because all the values in L have keys that are less than all those in R, we merely attach R where the rightmost Leaf rests in L.
Here is a picture:
          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.

Improved deletion algorithm

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:

  1. Within L, locate its rightmost node; say that it is W. Say that W's parent node is Z. Make W the root of the new tree, by:
  2. Z.setRight(W.left()), that is, parent Z links to the subtree that W holds. (This frees W to move to the root; recall that W is the rightmost node in L, meaning that W's right subtree must be a Leaf.)
  3. W.setLeft(L)
  4. W.setRight(R)
  5. Reset all parent links accordingly.
This technique naturally balances the tree over a period of deletions, when the technique is alternated with its dual, which moves the leftmost node from R to be the new root:
  1. Within R, locate its leftmost node; say that it is W. Say that W's parent node is Z. Now, make W the root of the new tree, by:
  2. Z.setLeft(W.right()), that is, parent Z links to the subtree that W holds. (This frees W to move to the root; recall that W is the leftmost node in R, meaning that W's left subtree must be a Leaf.)
  3. W.setRight(R)
  4. W.setLeft(L)
  5. Reset all parent links.
Again, by alternating the two deletion algorithms we rebalance the tree over a series of deletions.

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.