37B. Destructive Functions on Trees

A destructive function modifies a tree. For example, the following function replaces each item in tree t by its cube.

  void cubeAll(Tree t)
  {
    if(t != NULL)
    {
      t->item = cube(t->item);
      cubeAll(t->left);
      cubeAll(t->right);
    }
  }

Observe that the definition of cubeAll does a preorder treversal of t.

Parameter t does not need to be passed by reference to cubeAll because cubeAll does not need to change what t points to. It only changes the items in the node that t points to. So cubeAll uses call by pointer (since type Tree is the same as Node*).


Example: change a tree into its mirror image.

To change a tree t into its mirror image, it is just a matter of swapping left and right subtrees of the root of t, then changing each subtree of t into its mirror image. A base case for an empty tree is also important: to change an empty tree into its mirror image, do nothing.

  // swapTrees(A,B) swaps the trees in variables A and B.

  void swapTrees(Tree& A, Tree& B)
  {
    Tree temp = A;
    A = B;
    B = temp;
  }

  // changeToMirror(t) changes tree t into its mirror
  // image.  This is a destructive function: it modifies
  // the nodes in t.

  void changeToMirror(Tree t)
  {
    if(t != NULL)
    {
      swapTrees(t->left, t->right);
      changeToMirror(t->left);
      changeToMirror(t->right);
  }

Example: remove a node

Let's define function removeLeftmostNode(t), which changes tree t by removing the node that lies as far as to the left as possible from the root. For example, if t is

then, after removing the leftmost node, t is

and doing another step of removing the leftmost node yields

It is easy to find the lefmost node: just move to the left until a null left pointer is encountered. To remove leftmost node L, all we need to do is replace the left subtree of the parent of L with a the right subtree of L. That might sound tricky, since L does not contain a pointer to its parent. Indeed, if you use a loop, it is tricky. There is also another issue: if tree t has only one node, then instead of changing the left subtree of a node, we need to change t to hold NULL.)

But recursion handles both of those issues elegantly, so elegantly that you don't even notice them. Since t is passed by reference, it is the left pointer of the parent of t (or the pointer to the root if there is only one node). So it suffices to change t to be its right subtree, in the case where t's left subtree is empty.

We do need to handle the special case where t is empty; then there are no nodes, so we do nothing. Also, when a node is removed, it should be recycled back to the heap manager.

  void removeLeftmostNode(Tree& t)
  {
    if(t != NULL)
    {
      if(t->left != NULL)
      {
        removeLeftmostNode(t->left);
      }
      else
      {
        Tree hold = t;
        t = t->right;
        delete hold;
      }
    }
  }

Exercises

  1. Is the definition of removeLeftmostNode tail recursive? Answer

  2. Does the following correctly implement a nondestructive function to compute the mirror image of a tree?

      Tree mirror(Tree t)
      {
        Tree cpy = t;
        changeToMirror(cpy);
        return cpy;
      }
    
    Answer

  3. Write a definition of function destroy(T ), which deletes every node in tree T. Answer