Docstoc

A Binary Tree ADT - PowerPoint

Document Sample
A Binary Tree ADT - PowerPoint Powered By Docstoc
					A Binary Tree ADT
                   Fields
• The definition of a binary tree pretty much
  requires the following fields:
    Object value;
    BinaryTree leftChild;
    BinaryTree rightChild;
• I also wanted to have this additional field:
    BinaryTree parent;
• Should these fields be public, private, or
  somewhere in between?
            Maintaining control
• It is the responsibility of any class to ensure the
  safety and validity of its objects
• Is there any real harm in letting the fields of a
  node in a binary tree be public?
   – In other words, how easily can the binary tree be
     damaged?
   – I claim it’s quite easy, as I will demonstrate shortly
• In my design, leftChild, rightChild, and parent
  are private, and I provide getters and setters
• For consistency, the value field should also be
  private
             Getters and setters
• Here are the methods we have decided on so far:
     public BinaryTree getLeftChild()
     public void setLeftChild(BinaryTree child)
     public BinaryTree getRightChild()
     public void setRightChild(BinaryTree child)
     public BinaryTree getParent()
     public void setParent(BinaryTree parent)
      • We don’t want this one—why not?
          – Have to add parameter to say which child it will be
          – We can use setLeftChild or setRightChild instead

     public Object getValue()
     public void setValue(Object value)
                Header nodes
With a header:                    Without a header:
BinaryTreeHeader myTree           BinaryTree myTree




• I don’t like to use a header node for a binary tree,
  because that gets in the way of treating the
  subtrees as binary trees in their own right
                   Constructors
• There is an obvious three-argument constructor
  that we need:
   – public BinaryTree(Object value,
                       BinaryTree leftChild,
                       BinaryTree rightChild) { ... }
• In addition, we need a no-argument constructor
   – This is a requirement for serializable objects
   – public BinaryTree() {
        this(null, null, null);
     }
• Here’s a third constructor that I found convenient:
   – public BinaryTree(Object value) {
        this(value, null, null);
     }
public void setLeftChild(BinaryTree child)

  • Here is the obvious code for this method:
     public void setLeftChild(BinaryTree newChild) {
        leftChild = newChild;
     }
  • Is there anything wrong with this code?
  • Hint: yes
             Naive setLeftChild
• Remember that, in
  this design, a node   Before:                  parent
  also has a link to
  its parent
                        new child   left child      right child

• This version of
  setLeftChild
  does not              After:                   parent
  preserve the
  validity of the
  data structure        new child   left child      right child
public void setLeftChild(BinaryTree child)

  • Here is the improved code for this method:
     public void setLeftChild(BinaryTree newChild) {
        leftChild.parent = null;
        leftChild = newChild;
        newChild.parent = this;
     }                          NullPointerException
  • Now is this code correct?
  • Is it reasonable to set the left child to null?
     – A binary tree can be empty, so yes
  • What happens if we try to do so?
public void setLeftChild(BinaryTree child)

  • Here is even more improved code for this method:
     public void setLeftChild(BinaryTree newChild) {
        leftChild.parent = null;
        leftChild = newChild;
        if (newChild != null) {
            newChild.parent = this;
        }                      NullPointerException
     }
  • Do you see any more problems?
  • What if the left child was originally null?
public void setLeftChild(BinaryTree child)

  • Here is yet another version of this method:
     public void setLeftChild(BinaryTree newChild) {
        if (leftChild != null) {
            leftChild.parent = null;
        }
        leftChild = newChild;
        if (newChild != null) {
            newChild.parent = this;
        }
     }
  • Now is there anything wrong?
  • What if the new child already has a parent?
public void setLeftChild(BinaryTree child)

  • And yet again:
  • public void setLeftChild(BinaryTree newChild) {
       if (leftChild != null) {
           leftChild.parent = null;
       }
       leftChild = newChild;
       if (newChild != null) {
           if (newChild.parent != null) {
              newChild.parent.leftChild = null;
           }
           newChild.parent = this;
       }
     }
  • Now is there anything wrong?
public void setLeftChild(BinaryTree child)
  • Bad assumption: it was previously a left child
  •   public void setLeftChild(BinaryTree newChild) {
         if (leftChild != null) {
             leftChild.parent = null;
         }
         leftChild = newChild;
         if (newChild != null) {
             if (newChild.parent != null) {
                if (newChild.parent.leftChild == this)
                    newChild.parent.leftChild = null;
                else
                    newChild.parent.rightChild = null;
             }
             newChild.parent = this;
         }
       }
  • Now is there anything wrong?
           How much is enough?
• What if the new child is already a node elsewhere in
  the binary tree?
• Do we need to search the tree to find out?
   – This could be a somewhat expensive search—O(n)
   – All our previous modifications have been O(1), that is,
     constant time
• I think that this is a problem only if the new child is
  an ancestor of the node it is to be added to
   –   This is an O(log n) search, if the tree is balanced
   –   Is this worth doing?
   –   It’s a judgment call—how safe does our code need to be?
   –   The answer depends on what the code is for
                Getters and setters
•   Getters and setters are annoying to write, especially
    when they don’t seem to add any value to the code
•   There are two purposes:
    1. To prevent careless or malicious access to the object
       • You’ve just seen an example of this
    2. To preserve flexibility, in case you might want to change
       the object some time in the future
       • For example, if we did not originally have the parent
           link, the following code would have been enough:
               public void setLeftChild(BinaryTree newChild) {
                  leftChild = newChild;
               }
                  Taking stock
• Are the constructors and mutators (setXXX
  methods) adequate to construct any binary tree?
   – Yes, provided you start from the root and build the
     binary tree by working downwards
   – However, there isn’t much support for changing an
     existing binary tree
• Can we access all the data in the tree?
   – Yes, with the getXXX methods
   – However, it might be nice to provide convenience
     methods for testing if we are at the root or a leaf
  Convenience accessor methods
• It’s easy enough to test if we are at a root
  (parent==null) or at a leaf (leftChild==null &&
  rightChild==null), but this is so commonly
  needed that we might as well supply the methods:
   – public boolean isRoot()
   – public boolean isLeaf()
• Besides, using these methods makes the code
  more readable
       Changing the binary tree
• I’ve seen some pretty complicated methods for
  doing things in the binary tree
• The kind of changes that are needed in any given
  program are probably very problem-specific
• What I think we need is not a collection of
  complicated methods, but some very simple
  methods we can put together in complex ways
• Here’s what I have:
     /** Breaks the connection between this node and its
      * parent.
      */
     public void detach() { ... }
                  detach()
• After all we’ve been through with setLeftChild,
  detach is pretty trivial:

  public void detach() {
     if (parent != null) {
         if(parent.leftChild == this)
             parent.leftChild = null;
         else
             parent.rightChild = null;
         parent = null;
     }
   }
        Using existing methods
• It is frequently advantageous to use some of the
  methods of a class when implementing other
  methods
   public void setLeftChild(BinaryTree newChild) {
     if (leftChild != null) leftChild.detach();
     if (newChild != null) newChild.detach();
     leftChild = newChild;
     if (newChild != null) newChild.parent = this;
   }
          Serialization methods
• Since the binary tree needs to be serialized, I
  added the following two methods:

• public static BinaryTree load(String fileName)
  throws IOException

• public void save(String fileName) throws
  IOException
    Other input-output methods
• It’s always a good idea to write the following
  method (simplifies debugging):

  public String toString()


• Because a binary tree isn’t terribly easy to read
  when it’s shown linearly, I also wrote the following
  method to give me a nicely indented tree:

  public void print()
                     toString
         public String toString() {
           if (isLeaf()) return value.toString();
           StringBuffer buffer = new StringBuffer();
   root    buffer.append("[" + value + ", ");
           if (leftChild == null) buffer.append("null");
  left
           else buffer.append(leftChild.toString());
subtree
           buffer.append(", ");
           if (rightChild == null) buffer.append("null");
 right
           else buffer.append(rightChild.toString());
subtree
           buffer.append("]");
           return buffer.toString();
        }
                      print()
• toString is handy for producing condensed,
  single-line output, but doesn’t show the shape of
  the binary tree
• To keep track of indentation, we need either a
  global variable (bad) or a parameter (OK)
• I don’t want the user to have to supply this
  parameter, so:
     public void print() {
       print("");
     }

     private void print(String indent) { ... }
                print(String indent)
             private void print(String indent) {
                 System.out.println(indent + value);
        root     if (isLeaf()) return;

                    if (leftChild == null) {
                        System.out.println(indent + " " + "null");
                    }
 left subtree       else {
                        leftChild.print(indent + " ");
                    }
                    if (rightChild == null) {
                        System.out.println(indent + " " + "null");
                    }
right subtree       else {
                        rightChild.print(indent + " ");
                    }
                }
               Final comments
• I didn’t think of everything when I first wrote
  setLeftChild—my first version was pretty sloppy
   – Maybe there are still some problems I’ve overlooked
   – No program is ever perfect
   – Corrections are, as always, welcome
• I took some care because this is intended as an
  example of ADT design
   – For just using it in the Animals! program, I wouldn’t
     have been so fussy
   – However, even for just one program, using setters and
     getters is always a good idea
   – It’s impressively hard to tell beforehand how much a
     program is going to be used
The End

				
DOCUMENT INFO