Textbook+35 Trees
Textbook+35 Trees
Binary Trees
Chapters 33 and 34 gave you a healthy introduction to working with dynamic data
structures. Probably it was too healthy for many students' tastes. You'll be glad to
hear that the dynamic data structures with references story is not yet finished. There
is more, and it gets better all the time.
The linked lists shown in the previous chapters have considerable virtue. Memory
can be dynamically allocated. This is great because you do not have to be
concerned about how much memory to reserve at the time that you write a program.
At the same time the ability to insert, and delete linked list nodes, with a few
program statement changes is very efficient.
However, a little problem - possibly a big one - appears to be ignored. When you
insert a new node in a linked list, are necessary variables already referencing the
required nodes? When you delete an existing node, is there a reference identifying
the node to be deleted? The answer is NO to both questions. In both cases a search
needs to be performed to find the insert or delete location. Do you realize that this
search is a linear search? In every example that you have seen, some reference
identifies the first element in a linked list and the search proceeds from one node to
the next, in a smooth linear fashion.
It has been some chapters ago but you probably remember that a binary search -
provided a sorted array is used - is incredibly more efficient than a linear search.
Furthermore, the efficiency of the binary search increases tremendously as the
number of elements in the array grows larger and larger.
In other words, you accept the fact that insertion and deletion is more efficient with
a linked list, once the desired insertion area is found. However, you can search
much faster with an array, using a binary search, than you can search a linked list
with a linear search. This means that a static structure, like an array, is more
efficient with search algorithms and the dynamic linked list is more efficient for the
actual insertion and deletion process.
Suddenly, you are no longer so impressed with dynamic data structures, or at least
you are not impressed with linked lists. It seems like a bunch of work and you still
end up with the slow linear search approach that we condemned many chapters ago.
Well this chapter solves that problem. You are about to learn about binary trees.
Working with binary trees will allow you to perform the efficient binary search
technique that is possible with arrays and at the same time do the quick, memory
saving, insertion/deletion that is available with linked lists.
F A G A V
The linked list above can be changed to include a second set of linking references,
which links the nodes in the opposite direction, like the next illustration.
[F] [B]
Even with the second set of references, the list is still linear. Whichever direction
program execution takes with a linked list, there is only one possible node that can
be the next node. At the end of the last chapter you did learn about a linked list of
linked lists, which showed that it is possible to go in different directions from the
same node.
The single next node concept of a regular linear list is exactly what a tree does not
have. We'll come back to a binary tree soon enough, but for now, let us look at just
a plain general tree.
N1
N2 N3
N4 N5 N6 N7 N8 N9
The illustration above symbolizes a tree. This particular tree starts with Node1,
called the root, from which two references connect to two other nodes, Node2 and
Node3. Both Node2 and Node3 have three references that continue to connect to
additional nodes. The linking process shown is hardly a linear method, and does not
resemble the linked structures of the previous two chapters.
Let us right now define what a tree is. Logic should dictate that a tree is more
general then a binary tree. Definitions are presented in top-down style, from general
to specific. Defining a binary tree requires understanding a general tree first.
Tree Definition
N1
N2 N3
N4 N5 N6 N7
In the illustration of the binary tree, N1 is the root, N2 is the left child of N1, and
N3 is the right child of N1. At the same time N2 and N3 are the parents of the
next level of nodes, N4, N5, N6 and N7.
The terms, root, parent, left child and right child are official computer science terms used
with tree data structures in any programming language. These are not terms used only with
Java.
Binary Tree Definition
Binary tree structures bring a whole set of additional words in your computer
science vocabulary. You may consider some of the terms pretty corny, like the
parent and child terms that were just introduced. Keep in mind that these are
official computer science terms that have been accepted, and used, by major
universities and industries that teach or use computer science knowledge.
Root (node)
The root is the top node of a binary tree; it is the start of the binary tree; it is the
single node of the tree that allows access to all other nodes. Binary trees are,
traditionally, drawn upside down in pyramid fashion.
Root N1
N2 N3
N4 N5 N6 N7
Level
Level-0 N1
Level-1 N2 N3
Level-2 N4 N5 N6 N7
The nature of computer science is frequently to start counting at 0. Tree levels are
no different. This can bring about some confusion. What is the lowest level in the
tree with five levels? It is level 4. Look at the illustration above. The binary tree
has three levels, and the lowest level is level 2.
Note that the definitions that follow will make frequent reference to the binary tree
illustrations shown above.
Tree Node
A tree node is a single element in a binary tree. This node can be at any location in
the tree. The tree node includes the data, as well as references to other nodes at a
lower level in the tree.
Children
Nodes N2 and N3 are both children of the root, N1. Nodes N4 and N5 are children
of node N2 and nodes N6 and N7 are children or node N3.
Parents
Siblings
N2 and N3 are siblings, as are N4 and N5. N5 and N6 are on the same level, but
they are not siblings, since they do not share a common parent. You could say that
N5 and N6 are the same generation or cousins.
Ancestors
N1 is the ancestor of all the nodes that follow. An ancestor is either a parent of the
node, or grand parent, or great grand parent and so on. The ancestors of N6 are N3
and N1.
Descendants
Descendants are children of a node or grand children, or great grand children and so
on. The descendants of N1 are N2, N3, N4, N5, N6 and N7.
Subtree
Any node in a tree, and all its descendants is a subtree. In such a case the given
node becomes the root of the subtree. The subtree, which starts with N2 includes
N2 as the root and the descendants N4 and N5. In the previous illustrations we can
say that the left subtree of N1 starts with N2 and the right subtree of N1 starts with
N3.
Leaf
A node without any children is called a leaf. N4, N5, N6 and N7 are all leaves.
A path in a binary tree is a sequence of nodes with each node the parent of the next
node in the path. N1 – N2 – N4 is an example of a path.
Branch
A branch is the path that extends from the root of a tree to a leaf. A branch can also
start in a subtree with the same meaning. The node that starts the subtree becomes
its virtual root. N1 – N2 – N4 is an example of a branch.
Height
The height of a binary tree is measured by counting the number of nodes in the
longest possible path from the root of the tree to any of its leaves. The height is the
same as the number of levels in a binary tree. The height of the tree below is 4.
N1
N2 N3
N4 N5 N6 N7
Width
Now let us start getting a little more practical with binary trees. We will go another
step and custimize a binary tree to make it a binary search tree. The tree shown,
below on this page, is an example of a binary search tree. Notice how numbers are
placed in the tree. In every case you will see that the number value of the left child
is less than the parent's number, while the number of the right child is greater than
the parent's number.
500
300 700
575 825
The binary search tree is based on the concept of the subtree. Assume that data is
entered in the proper manner, so that a binary search tree exists. Suppose you wish
to find the record with account number 550. You start at the root, which is 500.
Since 550 is greater than 500 you can eliminate the entire left subtree of the root.
You move to the right subtree. The right child has a value of 600, which is greater
than 550. You move left and arrive at the node with 550 in it.
This process is identical to the binary search that you have seen used on a sorted
array. The binary search gets its efficiency from chopping data into large chunks
and eliminating such chunks that cannot possibly contain the required record. You
know from the previous chapter that references can link nodes together. The
drawings of most linked lists demonstrate a linear linking. The illustrations in this
chapter suggest that references can link one node to two other nodes.
Finally, we come to the main point of using a binary tree. With a binary tree, or
more specifically a binary search tree, we can use the searching efficiency of the
binary search that already exists with a sorted array. At the same time we get the
memory manipulation and insertion/deletion efficiency that linked lists show. In
other words we get the best of both worlds. Do we lose anything in the process?
Do binary trees sound too good to be true? They are pretty neat, but they can also
be fairly complex. Hopefully, you will consider the complexity worth it, when you
see the efficiency that is gained.
Figure 35.1
class TreeNode
{
public TreeNode(Object initValue, TreeNode initLeft, TreeNode initRight)
{
value = initValue;
left = initLeft;
right = initRight;
}
The majority of the program examples in this chapter will be using binary search
trees that store integer values. Integer values make it simpler to see how the binary
search tree is used. As has been my habit in previous situations, I will alter the
usual TreeNode class to make int storage simpler. I trust you understand the
process of the Integer wrapper class by now and it provides clearer program
statements to understand the program examples that follow. The altered TreeNode
class, which works only with int values in shown in figure 35.2.
Figure 35.2
class TreeNode
{
public TreeNode(int initValue, TreeNode initLeft, TreeNode initRight)
{
The simplest binary tree, which looks like a binary tree, has three nodes. The three
nodes are one root and two children. In this section we will strictly work with a
simple three-node binary tree to learn some important tree traversal techniques.
This approach was used with linked lists when the first introduction was only
concerned with two or three linked nodes. Remember that the remainder of this
chapter will constantly be mentioning binary trees when in fact they really are
binary search trees.
Keep in mind that binary search trees store data in such a manner that the data value
of the left child is less than the parent’s data value, while the data value of the right
child is greater than the data value of the parent.
The program examples in this section will involve a three-node binary tree that can
be visualized by the diagram in figure 35.3.
Figure 35.3
[root]
400 800
There is a good chance that program Java3501.java, in figure 35.4, will make
perfect sense to you. The knowledge you gained from working with linked lists
hopefully carries over to this chapter. It is also possible that you are confused and
you may argue that linked lists never made sense to you, so any knowledge that
would get transferred to the next chapter is hardly of the useful variety.
The first series of binary tree program examples are done with many program
statements without the benefit of any loop structures or methods to shorten the code.
The key issue of this section is to understand the fundamental linking strategy used
with binary tree structures. Program Java3501.java includes the TreeNode class,
which is actually included with each of the program examples. This is a reminder
that a special TreeNode class is used for storing int values. Future program
examples in this chapter will no longer include the TreeNode class.
This program constructs three TreeNode objects and links them in the manner of
the diagram, shown earlier. The value of each node is displayed by direct access to
each node. Keep in mind that it is quite unusual to have a reference for every node
in a tree. This may happen with a simple three-node tree, but will not be the case
for large tree structures.
Figure 35.4
// Java3501.java
// This program creates a three-noded binary tree. Each tree node is
// accessed directly with its own reference variable.
class TreeNode
{
public TreeNode(int initValue, TreeNode initLeft, TreeNode initRight)
{
value = initValue;
left = initLeft;
right = initRight;
}
Java3501.java Output
JAVA3501.JAVA
Access to a linear data structure is provided by an identifier that references the first
node in the structure. In the past you have seen names like front, top, and first.
With binary trees it is customary to use identifier root to indicate the first node
where access starts in a binary tree. Program 3502.java, shown in figure 35.5,
displays the same tree values as before, but this time it is done by only accessing the
This example also shows an InOrder Traversal. Inorder traversals follow the
sequence of left-child, parent, right-child. A very practical benefit emerges from an
inorder traversal. You will note that the data displays from smallest values to largest
value. This feature is only true for inorder traversals of binary search trees.
Figure 35.5
// Java3502.java
// This program accesses the left-child and right-child nodes indirectly
// from the root node and performs an inorder tree traversal.
System.out.println();
}
}
Java3502.java Output
JAVA3502.JAVA
Are there other ways to traverse a binary tree, besides the inorder traversal? There
are and the purpose of such traversals will be explained later. It is possible to
traverse preorder, which follows the sequence parent, left-child, right-child. It is
also possible to use the left-child, right-child, parent sequence, which is called
postorder traversal. Program Java3503.java, shown in figure 35.6, will
Figure 35.6
// Java3503.java
// This program accesses the left-child and right-child nodes indirectly
// from the root node and performs a preorder tree traversal.
System.out.println();
}
}
Java3503.java Output
JAVA3503.JAVA
Figure 35.7
// Java3504.java
// This program accesses the left-child and right-child nodes indirectly
// from the root node and performs a postorder tree traversal.
System.out.println();
}
}
Java3504.java Output
JAVA3504.JAVA
Traversing a binary tree by using an output statement for each node may not be so
bad for a three-node tree. It is hardly efficient if the tree has many nodes. A loop
structure, or similar repetitive structure, needs to be used. After all, a binary tree
may have thousands of nodes. With linked lists there were few problems traversing
from one node to the next node. Start with some reference first at the first node in a
linked list, and the program segment in figure 35.8 will visit and display every node
in the list.
Figure 35.8
while (first != null)
{
System.out.println(first.getValue());
first = first.getNext();
Such an approach is fine for linked lists. Linked lists are comfortable linear
structures, but binary trees are hardly linear. Each node is potentially linked to two,
one, or zero nodes. An even greater problem occurs since all references go in one
direction. There is no reference that allows traversing from child to parent. You
can move down, but you cannot move up. Once you are at a leaf, anywhere in the
tree, you are stuck with no means to continue elsewhere. You could place a
reference at each and every node, but that defeats the whole concept of a dynamic
data structure, let alone the incredible inconvenience of programming many, many
lines of code. Is there is way out of the binary tree dilemma?
This is going to surprise you. It only takes a few program statements in a method to
traverse a binary tree. The secret is recursion. Remember good old yukky, and
hard-to-understand, difficult-to-write, recursion? You probably did not see much
use for recursion. Well there is plenty of use for it with binary trees. Program
Java3505.java, shown in figure 35.9, shows you how to traverse a binary tree
recursively. You might argue that the traversal is used for the same, silly three-
node binary tree, but that does not matter. This traversal method will work for
binary trees of any size.
Figure 35.9
// Java3505.java
// This program demonstrates inorder tree traversal with a
// recursive tree traversal method.
traverseInOrder(root);
System.out.println();
}
Java3505.java Output
The main method is pretty much the same code as the previous program. It creates
the three-node binary tree. The only difference, and important difference, is a
method call to traverseInOrder(root) rather than three output statements.
Method traverseInOrder is small enough, but then you are not impressed. The
method has a few lines and the tree has few nodes. It seems fair. You might be
more impressed if you knew that the short method will also visit and display every
value of a binary tree with 5000 or more nodes. It can in fact perform just such a
task, but that will be demonstrated a little later.
A few small changes will be made to the recursive traversal method and it results in
a preorder and postorder traversal. Program Java3506.java, shown in figure 35.10
will demonstrate the preorder traversal method. Program Java3507.java, shown in
figure 35.11 will demonstrate the postorder traversal method.
Look at the traversal methods very closely and see if you note a pattern that makes it
easy to recognize each method. Each method has the same number of lines, but
somehow each method manages to traverse in a different manner.
Figure 35.10
// Java3506.java
// This program demonstrates preorder tree traversal with a
// recursive tree traversal method.
traversePreOrder(root);
System.out.println();
}
Java3506.java Output
Are you seeing a pattern yet? Pay attention at the location of the output statement
relative to the two recursive calls. The inorder traversal placed the output statement
in between the two recursive calls and now you see that the preorder traversal
locates the output statement prior to the two recursive calls.
Figure 35.11
// Java3507.java
// This program demonstrates postorder tree traversal with a
// recursive tree traversal method.
traversePostOrder(root);
System.out.println();
}
Java3507.java Output
You may not be particularly impressed by the preorder and postorder traversal
methods. However, the inorder traversal shows great practical value because it
sorts the data and nodes are traversed in the ascending order of the stored data.
This is great, but what if you wish to traverse in descending order. This certainly
is the case when you wish to display a list that starts with the highest scores down
to the lowest score on some test. You can accomplish such node access with a
reverse inorder traversal demonstrated by Java3508.java, shown in figure 35.12.
Once again observe the pattern. The output statement is between the recursive
calls as is the inorder traversal, but now the calls are switched.
Figure 35.12
// Java3508.java
// This program demonstrates reverse-inorder tree traversal with a
// recursive tree traversal method.
traverseReverseInOrder(root);
System.out.println();
}
Java3508.java Output
Somehow in a strange and mysterious fashion, a very small recursive method does a
bunch of miraculous traversing, and performs different types of traversal with a
small reshuffle of its statements. Many different traversals are buzzing around in
your brain. Let us try and clear things up to make a little more sense. We will
discuss each traversal that was shown earlier.
In the traversal definitions that follow, an interesting pattern will emerge that makes
it easy to remember which code to use. In each case compare the relationship of the
parent with the children and then look at the traversal method and check the
relationship between the output statement and the two recursive calls.
You will find a similar pattern with the three different binary tree traversals. Think
of the parent as the operator and the children as operands. With that comparison in
mind consider the following traversal definitions.
The in-order traversal starts with a recursive call to the left subtree.
After the output statement, a recursive call is made to the right subtree.
The in-order traversal sequence of a binary tree is left child - parent - right
child.
After the data display, a recursive call is made to the left subtree, followed by a
recursive call made to the right subtree.
The pre-order traversal sequence of a binary tree therefore is parent - left child -
right child.
The post-order traversal starts with a recursive call to the left subtree, followed
by a recursive call to the right subtree.
The method finishes with the output data display. The post-order traversal is left
child - right child - parent.
The binary tree traversal method has three program statements that use the sequence
of left recursive call - right recursive call - display data.
After you study these methods for a while, and use them in a program you will
probably conclude that the traversal methods do in fact work properly. You are
amazed and you are probably happy that something so short accomplishes such a
complex mission. This brings to mind the main reason for using recursion.
This reason for using recursion is difficult to believe until you see the binary tree
traversal methods. You may still be skeptical and feel that the traversal methods are
short, but there is nothing simple about the recursive mumbo jumbo. Well nobody
said recursion is easy to understand, but consider the alternative. An iterative
method that traverses a binary tree is no picnic by anybody's standards. Your
teacher may be inclined to give you extra credit for writing a method iteratively that
can traverse a binary tree.
You can also take comfort in the fact that it is quite normal to use methods properly
and not understand how they work. Students taking the AP Computer Science
Exam will appreciate a short traversal method, even if the exact logic is a little hazy.
During the recursion chapter there were three fundamentals of recursion
introduced. You will find that the tree traversal methods can be explained by using
those three fundamental recursion principles.
Linked lists allow very efficient insertion and deletion of data. This was a big plus
for linked lists along with the efficient allocation of memory at execution time. We
did notice the problem with finding the location for the insertion or deletion process
with a linked list. A sorted array manages the ultra-efficient binary search because
every array index can be accessed. This is not possible with a linked list. It is
In this section you will learn how to create a binary search tree. We will combine
the logic of the binary search, shown earlier with arrays, with the insertion process
of a linked list. The result will combine the virtues of the sorted array with the
benefits of the linked list.
Actually, you have already seen the program code for creating a binary search tree
earlier in the chapter. You saw how little three-node trees were created and you
were shown three traversal methods for displaying the contents of the three little
binary search trees.
You are now very excited about traversing binary trees, but traversing these little
three-node trees is not real thrilling. You want to move up to the big time and create
some very serious trees. Program Java3509.java, shown in figure 35.13, handles
that issue with a special method that builds a binary search tree. This program will
take 40 random integers and place them in a binary search tree. The numbers are
displayed, as they are generated, and then are displayed again after the tree is
finished. The second display, accomplished with an in-order traversal, will
demonstrate a pleasant side benefit. The display output is a sorted list of integers.
This is another one of those sections - all reference linking code appears that way -
where you need to follow the code with pencil and paper and draw pictures of the
binary tree and its links as the tree grows. Drawing pictures will not go away.
Linked structures, whether singly linked lists, doubly linked lists or binary trees can
have logic problems that the computer will not catch.
Figure 35.13
// Java3509.java
// This program creates a binary search tree (BST) with 40 random integer
// values. The values are then displayed with an inorder traversal.
import java.util.Random;
6251 6080 9241 1828 4055 2084 2375 9802 2501 5389
2517 1942 5390 3806 3012 2384 8787 5303 8532 6175
3801 5351 2792 7316 7428 6781 1425 8943 2871 3439
4729 8397 7501 5825 9903 3555 8952 1831 7010 5108
1425 1828 1831 1942 2084 2375 2384 2501 2517 2792
2871 3012 3439 3555 3801 3806 4055 4729 5108 5303
Method createBST has similarities to the insertion method that was shown with the
linked list chapters. Two major components are necessary for inserting a node
properly. The first component is to find the location in the tree and leave strategic
references at certain key nodes. The second part is to link the new node with these
temporary nodes to the existing tree.
Program line #1 defines local variables. Variables t1, t2 and t3 are used to build the
binary search tree.
Program lines #2 and #3 allocate memory for the root node of the tree. A random
integer is assigned to the data field and null is assigned to the left and right
linking references of the root node.
Program line #4 assigns the root value to t2 and t3. These two reference
variables are ready to traverse along the tree and find the proper position to insert
the new tree node.
t2 = t3 = root; // #4
Program line #5 establishes the loop structure. One tree node, the root, has been
created, and 39 tree nodes still need to be built into the tree.
Inside the loop structure each iteration starts by allocating space for a new tree
node with program lines #6 through #8. Line #6 generates a random integer
The next loop structure is designed to find the proper insert location of the new
tree node. Reference t2, in line #9 takes the lead and is used in the while
condition. In line #10 reference t3 follows reference t2. The result will be that
the new tree node can be inserted between the nodes referenced by t3 and t2.
Line #11 takes advantage of the properties of a binary search tree and compares
the data of t1 (the new tree node) with the data of the traversing reference t2.
Program lines #12 and #13 move t2 to the left or right based on the comparison
of line #11.
Keep in mind that any new tree node will always be attached as a leaf. It is the
job of the t2 reference to traverse along the tree until t2 becomes null. In the
process the t3 variable, which trails one step behind, will reference the node that
will link to the new tree node. The t2 reference does not perform any linking, but
it assists in helping t3 find the correct node. This in turn allows the t3 to link to
the new tree node.
t2 references the correct node, but there remains the issue about linking with the
right or left reference. Line #14 makes a comparison. If the new node’s data is
greater than the t3 data, a link is made with the right reference, which is line #15.
Line #16 links with the left reference when the if condition is false.
t2 = root; // #17
return root; // #18
A very significant process has been explained in this section. A Binary Search Tree
has been built and traversed. It is possible that you overlooked the fact that the
traversal, if done in-order, will display the numbers sorted from smallest to largest.
In case you did not notice with the small quantity of numbers involved, this sort is
extremely fast. It beats the socks off a bubble sort, selection sort or insertion
sort. The binary tree sort is in the same speed efficiency league as the merge sort,
you studied in the recursion chapter.
It is amazing how many different ways are available to traverse a binary tree. You
may think that four traversal methods, inorder, preorder, postorder and reverse
inorder, provide plenty of ways to visit nodes on a tree. Well, the traversal story is
not finished yet. There remains one more traversal to investigate. What if you need
to display (or visit) the nodes of a binary tree level by level. Not pre-order, not
post-order, not in-order and not even reverse order, just simply by level.
500 300 700 200 400 600 800 150 250 350 550 750 850 575 825
Figure 35.14
500
300 700
575 825
It is easy enough to explain the meaning of a level traversal. Is it also easy to write
the program code that will achieve such a goal? Surprisingly, considering the
previous tree traversals, a level traversal is not recursive. Program Java3510.java,
in figure 35.14, creates a BST and traverses the tree by levels. The program
requires the use of the TreeNode class, shown in figure 35.15, and the LinkedList
implementation of the Queue interface.
Figure 35.14
// Java3510.java
// This program demonstrate the use of the iterative <levelTraverse> method
import java.util.*;
Figure 35.15
class TreeNode
{
public TreeNode(int initValue, TreeNode initLeft, TreeNode initRight)
{
value = initValue;
left = initLeft;
right = initRight;
}
The program output in shown in figure 35.16. The set of data is tested twice,
entered in a different order each time. Recall that the order in which data is stored
impact the shape of the binary tree. Take the two sets of data and draw two binary
trees and then check the actual tree with the level traversal display.
JAVA3510.JAVA
Do what I did. Start by drawing a diagram of an actual binary tree. Make the tree
small, like the ones used with the program example. Now play computer, start at
the root and follow the code line-by-line. The code is not really complex and you
will find that it does make sense. You will also find that it is not so bad and
surprisingly logical. Method levelTraverse is isolated on the next page to allow
easier focus.
Method levelTraverse
You have already worked with queues before. In previous chapters you have seen
queues implemented with static, as well as dynamic data structures. The level tree
traversal can work with any queue implementation, as long as the queue element is
an integer.
Are there any surprises in method levelTraverse? There should be because it is the
first binary tree method that is not recursive. This is an honest to goodness iterative
method that does the job just nicely for us. A second difference is that a queue is
used in this case. All the other recursive methods rely on a stack to properly
perform the processing and store intermediate values.
This is going to be a pretty tough section. Deleting nodes from a linked list is similar, in logic
and difficulty, to inserting nodes in a linked list. Unfortunately this is not the case with binary
trees. Deleting nodes from binary trees is a completely different story from inserting nodes in a
binary tree. The most important thing to realize about deleting node from a binary tree is that
you must consider the different types of nodes that need to be deleted. Each case gets its own
special treatment.
Three Delete Node Possibilities from a Binary Tree
The situation of deleting a leaf can be demonstrated with the binary tree. Suppose
that the leaf node with "C" needs to be deleted from the tree.
B E
A C F
B E
A F
B E
A C F
B F
A C
The third deletion situation gets pretty messy. Consider the drawing, below, of a
binary tree segment. In this example you want to delete the node that contains B.
Unfortunately, node B has two children. What needs to be done with the left
reference of node D? Is it hooked up to node A or to node C?
B E
A C F
A E
C F
C E
A F
Now that you know that there are three cases. Remember that cases are a big deal.
All too often students write solutions for an easy case, or some general case. We do
have three possible delete cases and that needs to become part of our
implementation. There exist algorithms that very cleverly delete nodes for all
situations. I have found that many of these methods are very functional, but the
code is difficult to follow. I have intentionally created separate methods for separate
cases so that the program logic is easier to follow.
Figure 35.17
// Java3511.java
// This program sets the stage for the <DeleteDemo> class. In this program
// the classes and methods used to create and display the BST that will be used for
// the deletion program are shown first.
import java.util.*;
System.out.println("\n\n");
}
class DeleteDemo
{
private TreeNode root;
class TreeNode
{
public TreeNode(int initValue, TreeNode initLeft, TreeNode initRight)
{
value = initValue;
left = initLeft;
right = initRight;
}
public int getValue() { return value; }
public TreeNode getLeft() { return left; }
public TreeNode getRight() { return right; }
public void setValue(int theNewValue) { value = theNewValue; }
public void setLeft(TreeNode theNewLeft) { left = theNewLeft; }
public void setRight(TreeNode theNewRight) { right = theNewRight; }
private int value;
private TreeNode left;
private TreeNode right;
}
JAVA3511.JAVA
You must start out by realizing that it is not possible to just start by deleting a node.
Access to a binary tree starts at the root node and it is necessary to find the node
that requires deleting before any further process is attempted. This means that my
deleteNode method, shown in figure 35.19, resembles some type of search method
more than anything else. Earlier in this section you learned that there exist three
primary delete cases. I have decided that it is possible during the searching for a
node process, to determine which delete case applies and then call the appropriate
method.
The first part of the deleteNode uses a loop to stop reference p at the requested
node. Reference temp is intentionally trailing one node back to be at the correct
location for the later deletion process. The second half of the deleteNode method
determines which delete case is appropriate.
If the left-child and the right child are both null, the node must be a leaf. If either
the left-child or the right-child is null, the node must be a parent with one child.
Finally, the only other possible node is a parent with two children.
We can now continue and proceed from simple to complex. First consider the
business of deleting a leaf. Now before we get too excited you must realize that
there are three primary cases, and they have been mentioned. But there are also
some subtle other cases that you must realize. Deleting a leaf that is a child is one
matter, but it is also possible that a binary tree has only one node. The deleteNode
method regards a leaf, which is a root, a leaf nevertheless. Deletion of the root has
some special considerations.
Deleting a normal (non-root) leaf is comfortable. Change the link that connects the
leaf to its parent to null. Reference temp has been patiently waiting at the parent of
the p leaf node for instructions. The only issue remains is whether the left link or
the right link becomes null. An if..else condition determines the direction of the
linmk that must be broken by setting it to null and method deleteLeaf is finished.
We now continue with the business of deleting a node that is a parent with one
child. It is not possible to simply yank this parent node from the tree. There is a
little matter of a child and who better to take care of the child than its grandparent?
The trusty temp reference waits at the grandparent node and manages the link to the
child to avoid creating an orphan.
Once again there is the root issue. You might delete a parent with a single child and
the parent is the root of the tree. There is no grand parent in sight and now the
solution is different. Figure 35.21 shows the deleteParent1 method. The same test
used with deleteLeaf, the if (p == temp) condition determines if we have a root
type situation. If such is the case the root needs to take a hike left or right
depending where the child is located. Basically, the only child is promoted to root.
If there is a parent involved, which is not a root, you will run into a bunch of nested
if...else statements. The temp reference is checked to determine if the link between
grand parent and parent is left or right. A secondary and nested condition needs to
determine if the child is linked left or right as well. The end result is that the proper
link from the grandparent bypasses the parent - hence deleting the parent node - and
connects to the grandchild. You may wish to back up to the diagrams that were
presented earlier for the different delete scenarios.
Figure 35.21
You may rapidly be losing your enthusiasm for this deletion section and you have
not yet reached the grand finale where a parent with two children is deleted. It is the
one case where it is not necessary to have a temp reference training behind. Figure
35.22 shows the final installment in the delete trilogy. The code is not so long but
not as comfortable as the previous delete methods.
This is not a case where parents, grand parents, uncles or cousins are linking up in
some fashion that eliminates one of the relatives. In the parent-with-two-children a
strange reality occurs. If you check the code carefully, you will find that the
designated node actually is not deleted. The designated delete node receives a new
value provided by another node, which is deleted. Strange? Perhaps, but this is a
definite case where you gain far more understanding from tracing than from me
droning on about this process. Get yourself some paper. Draw a simple binary tree
and delete various nodes with two children while tracing through the code of
thedeleteParent2 method. You do this two or three times and there will be some
lights starting to flicker brighter and brighter.
What remains now is the complete program and a thorough test of our three
methods. The main method in Java3512.java, shown in figure 35.23, deletes
eight times, which includes one attempt to delete a non-existing node. Every
possible type of binary tree node deletion case is considered and tested. You will
find success is every scenario. It looks like our deletion methods pass the test.
Figure 35.22
// Java3512.java
// This program tests the DeleteDemo class, which creates a binary search tree
// and tests every possible delete scenario.
import java.util.*;
System.out.println("\n\n");
}
}
class DeleteDemo
{
private TreeNode root;
There exist a considerable variety of binary trees. Definitions and examples of these
different types of binary trees are compiled in this section for your convenience.
Some differences are subtle and you need to pay close attention.
The definitions will be given in a sequence so that later definitions can include
reference to earlier terms that have already been defined. Both tree and binary tree
definition are repeated here to help make the section a complete study reference.
There are many real-life applications that involve a general tree. The hierarchy of
many organizations, the animal and plant classification systems, and your family
tree are three examples. At this stage of computer science we will focus our
attention on the properties peculiar to binary trees.
Do keep in mind that the binary tree definitions that follow are logical (abstract)
data structures. In this chapter we have implemented binary trees with dynamic
data structures, but it is very possible to create these trees with static structures as
well.
Each one of the different tree structures will first present a definition and then
display a diagram of the specified tree. Study and remember each definition and
visualize the diagram. You will encounter these different trees quite frequently.
N1
N1
N2 N3
N4 N5 N6 N7
- +
A B C D
(A - B) * (C + D)
500
300 700
575 825
NOTE:
As odd as it sounds, a tree with 0 nodes is considered full.
500
300 700
500
300 700
500
300 700
MaxHeap Example
500
450 475
MinHeap Example
100
200 300
Suppose that you have created a binary search tree with all the data properly
entered. You know the various ways that exist to display the information in the tree
nodes. For this particular display you wish to display the data, which are integers,
sorted from smallest number to largest number.
This should be a comfortable method and a recursive in-order tree traversal will do
the job very nicely. But wait, you want more. You will also want to place a title
above the display of the numbers. In other words you want program output that
looks something like figure 35.23.
Figure 35.23
----------------------------------------------------------
=============== INORDER TREE TRAVERSAL ===============
----------------------------------------------------------
This seems like a simple order. You already know how to create a tree and traverse
a tree. It seems that the only requirement is to add a couple output statements and
the required job is finished. This is done by program Java3513.java, in figure
35.24 by slightly changing an earlier program. Is the program output to your
satisfaction?
Figure 35.24
// Java3513.java
// This program demonstrates the consequence of casually placing some output statements in
// a recursive method.
import java.util.*;
if (p != null)
{
traverseInOrder(p.getLeft());
System.out.println(p.getValue());
traverseInOrder(p.getRight());
}
}
JAVA3513.JAVA
----------------------------------------------------------
=============== INORDER TREE TRAVERSAL ===============
----------------------------------------------------------
----------------------------------------------------------
=============== INORDER TREE TRAVERSAL ===============
----------------------------------------------------------
----------------------------------------------------------
=============== INORDER TREE TRAVERSAL ===============
----------------------------------------------------------
----------------------------------------------------------
=============== INORDER TREE TRAVERSAL ===============
----------------------------------------------------------
Such an output seems simple enough to create, but the recursive nature of the in-
order tree traversal can cause some serious oddities. Placing the method title inside
the traversal method will display the title for each time that the method is called.
This is not exactly what we have in mind. The solution to this problem is to use an
auxiliary method. This amounts to using two methods in place of just a single
traversal method. The technique is not complicated. In fact, you have probably
used this approach quite frequently without giving it much thought. Program
Java3514.java, in figure 35.25, uses Method displayTree to display the desired
Figure 35.25
// Java3514.java
// This program solves the problem of the previous program by using
// an auxiliary method.
import java.util.*;
JAVA3514.JAVA
----------------------------------------------------------
=============== INORDER TREE TRAVERSAL ===============
----------------------------------------------------------
100
200
300
400
500
600
700
Another example occurs when you have to write a method that returns the sum of
values stored in the nodes of a binary tree. In fact, this problem was given to
students during the very first AP Computer Science Examination. Many students
remembered the tree traversal method and used method traverseInorder as an
auxiliary method to the treeSum method. Program Java3515.java, shown in figure
35.26, demonstrates this process. You will also note that a static variable sum is
declared. This global variable allows the accumulation of node values in a recursive
method. This is not exactly the preferred program design. You will learn shortly
that there are superior approaches to this type of problem.
Figure 35.26
// Java3515.java
// This program uses an auxiliary method and a static sum to add the values in the
// nodes of a binary tree.
import java.util.*;
Java3515.java Output
JAVA3515.JAVA
Method treeSum initializes sum to zero and then calls method traverseInOrder.
Variable sum is a static "global" variable, which allows the value of sum to be
accumulated without any problems.
Here you are in chapter 35, which is pretty close to the end of this Exposure saga.
Even at this late in the game Mr. Schram keeps doing weird stuff to mess with your
mind. Take a quick inventory. The last section taught you, perhaps convinced you,
that auxiliary methods are a good thing. Now immediately after that section a weird
title pops up that seems to question the wisdom of using auxiliary methods.
The two program examples of the previous sections were intentionally selected.
Program Java3514.java, in figure 35.25, is a classic case for using an auxiliary
method. You want a title heading displayed above some data output, and a single
display is tough to achieve with a recursive method.
At first it may seem that method treeSum can only be done with an auxiliary
method. This is not such a bad idea because you are seeking a solution in an area
where you have comfort. You know how to traverse a tree, so why not use this
knowledge. Such a solution starts with the assumption that you must traverse the
binary tree to solve the addition problem. This is not necessarily true. Get away
totally from the idea of using any of your known traversal methods. In fact get
away from thinking void methods and handle the requirements strictly within a
return treeSum method.
Hopefully, this section will demonstrate that more desirable solutions are available,
and they are not necessarily more complicated. What is required is a different
approach, a different way of thinking about the same problem. Forget the tree
traversal, and look at the problem with a very simple tree in mind. What is the very
simplest tree? An empty tree. Good, now what is the sum of the values in an empty
tree? Zero. Sounds like a base case to me. Still good, now consider a simple tree
of three nodes, such as the one shown below.
500
400 600
Now what must be done if the tree gets more complicated? You will still need to
take the value at the root and then add the sum of the left subtree, as well as the sum
of the right subtree, to get the total sum. Sounds pretty recursive to me. Check out
the method treeSum in program Java3516.java, in figure 35.27. Does it seem
simpler than the double method approach?
Figure 35.27
// Java3516.java
// This program uses a single <treeSum> method to handle the addition
// of the values in the binary tree nodes.
import java.util.*;
Isn’t method treeSum nice and short. College folks call that type of solution
elegant. I personally really like the solution, although I am not sure if the word
elegant comes to my mind. The solution is truly in the spirit of recursion. Think
One example may not convince you that single return methods are all that terrific.
Yes the method solution sure seems shorter than the double-method solution, but
that is only one example. In the next section you will see many more examples of
methods that solve certain binary tree statistics in far less code than using tree
traversals.
It is not uncommon to see students shy away from these types of return method
solutions. Many students feel more comfortable with auxiliary void traversal
methods. Method solutions, especially recursive solutions, are often simpler to
create when you start thinking about a single method solution.
Method treeSum, shown in figure 35.27, is but one of a large variety of binary tree
methods that return some statistic of a specified binary tree. This section will add a
wide variety of commonly used binary tree methods. You will find that the majority
of the binary tree methods are return methods.
The algorithmic logic of the methods in this section will not be explained. The
definitions of the methods and the source code are presented here for students to
study and discover the logic of the solutions. Your knowledge of recursion and
comfort level with recursive methods will increase by studying and understanding
this material.
Several void methods and return methods, which were presented earlier, are
repeated here in an effort to compile an efficient section of popular binary tree
algorithms and their methods.
The next section will place all these methods into one large program that will
demonstrate the use of the methods as well as test their accuracy. This large
program will make much more sense after you have first done a method-by-method
analysis in this section.
Method inOrder
This method will traverse a binary tree in the sequence left child-
parent-right child. If a binary tree is a binary search
tree, the traversal will visit nodes from the smallest value to
the greatest value.
Method postOrder
This method will traverse a binary tree in the sequence left child-
parent-right child.
Method levelOrder
Method treeSum
Method leafCount
Method mirrorTree
Method getHeight
Method isFull
After many chapters you have certainly learned that everything presented in
Exposure Java is always done with complete workable programs. In the previous
section, multiple binary tree methods were not shown as part of a program. It would
have been tedious for each method example to make it part of a complete program.
Program Java3517.java, in figure 35.28, puts all the binary tree methods together
in one large binary tree statistics program.
In this section all those methods are grouped together into a large program that will
display the statistics of a binary tree. A rather artificial tree is created of seven
nodes. It is quite difficult to create a full binary tree with randomly generated
numbers. It is quite easy to alter the quantity and types of integers in the array that
forms the binary tree to test different scenarios.
Figure 35.27
// Java3517.java
// This program combines a large variety of binary tree methods into
// one program, which displays tree statistics and tests the tree methods.
import java.util.*;
class TreeNode
{
public TreeNode(int initValue, TreeNode initLeft, TreeNode initRight)
{
value = initValue;
left = initLeft;
right = initRight;
}
JAVA3517.JAVA
Level Traversal
400 200 600 100 300 500 700
InOrder Traversal
100 200 300 400 500 600 700
PreOrder Traversal
400 100 200 300 500 600 700
PostOrder Traversal
100 200 300 500 600 700 400
Node Count: 7
Leaf Count: 4
Tree Height: 3
A tree is a data structure that starts with a root node that can be linked to one or
more additional nodes. Furthermore, each node can be linked to one or more
additional nodes.
A binary tree is a tree data structure, in which each node can be linked to no more
than two other nodes, called left child and right child.
The root is the top node of a binary tree; it is the start of the binary tree; it is the
single node of the tree that allows access to all other nodes.
A binary tree has different levels. The root is always level-0, the nodes connected to
the root are at level-1 and any nodes connected to level-1 nodes are at level-2.
A tree node is a single element in a binary tree. This node can be at any location in
the tree. The tree node includes the data, as well as references to other nodes at a
lower level in the tree.
Children are nodes directly connected to the same node at one higher level.
Left children are linked to the left reference of a parent and right children are
linked to the right reference of a parent.
Ancestors are all the parents of a node, like grand parents or great grand parents.
Descendants are all children of a node like grand children or great grand children.
Any node in a tree, and all its descendants is a subtree. In such a case the given
node becomes the root of the subtree.
A path in a binary tree is a sequence of nodes with each node the parent of the next
node in the path.
A branch is the path that extends from the root of a tree to a leaf. A branch can
also start in a subtree with the same meaning. The node that starts the subtree
becomes its virtual root.
The width of a binary tree is measured by the number of nodes in the longest
possible path in the tree from one leaf to another leaf.
A binary search tree is a binary tree, in which the left child, if it exists, contains a
lesser value than the parent. The right child, if it exists, contains a greater value than
the parent.
An in-order traversal is a binary tree traversal that visits each node in the binary
tree with the sequence: Left Child - - - Parent - - - Right Child
A pre-order traversal is a binary tree traversal that visits each node in the binary
tree with the sequence: Parent - - - Left Child - - - Right Child
A post-order traversal is a binary tree traversal that visits each node in the binary
tree with the sequence: Left Child - - - Right Child - - - Parent
A level traversal accesses every node in a binary tree, starting at the root, and then
continues with each level of the binary tree. Each level in the tree is traversed from
left to right.
There are three primary cases considered when deleting nodes from a tree:
Delete a leaf - Delete a parent with one child - Delete a parent with two children
A binary expression tree is a binary tree, in which each node contains an operand
or operator of a mathematical expression. Each parent node contains an operator
and each leaf contains an operand.
A heap is a complete binary tree with the property that every parent has a value
that is greater or smaller than its children. If the parent's value is greater than the
children's values, the heap is called a maxheap. If the parent's value is smaller than
the children's values, the heap is called a minheap.
Many binary tree methods were introduced in this chapter. Section 35.12
summarized all these binary tree methods.