Binary Trees Complete Lesson
Binary Trees Complete Lesson
223
Chapter 11
Binary Trees
The data structures presented so far are predominantly linear. Every element has one unique
predecessor and one unique successor (except the first and last elements). Arrays, and singly linked
structures used to implement lists, stacks, and queues all have this distinctive linear characteristic. The
tree structure presented in this chapter is a hierarchical data structure where each node may have more
than one successor.
Goals
11.1
Trees
Trees are often used to store large collections of data in a hierarchical manner where elements are
arranged in successive levels. For example, file systems are implemented as a tree structure with the
root directory at the highest level. The collection of files and directories are stored as a tree where a
directory may have files and other directories. Trees are hierarchical in nature as illustrated in this
view of a very small part of a file system (the root directory is signified as /).
/
.Spotlight-V100
test
bin
sleep
etc
...
sync
...
nanorc
...
mail.rc
...
Each node in a tree has exactly one parent except for the distinctive node known as the root. Whereas
the root of a real tree is usually located in the ground and the leaves are above the root, computer
Chapter 11
224
scientists draw trees upside down. This convention allows us to grow trees down from the root since
most people find it more natural to write from top to bottom. You are more likely to see the root at the
'top' with the leaves at the 'bottom' of trees. Trees implemented with a linked structure can be pictured
like this:
root
nodes
1
2
4
3
5
edges
leaves
A tree is a collection of nodes with one node designated as the root. Each node contains a reference to
an element and has edges connecting it to other nodes, which are also trees. These other nodes are
called children. A tree can be empty have no nodes. Trees may have nodes with two or more
children.
A leaf is a node with no children. In the tree above, the nodes with 4, 5, and 6 are leafs. All nodes
that are not leaves are called the internal nodes of a tree, which are 1, 2, and 3 above. A leaf node
could later grow a nonempty tree as a child. That leaf node would then become an internal node. Also,
an internal node might later have its children become empty trees. That internal node would become a
leaf.
A tree with no nodes is called an empty tree. A single node by itself can be considered a tree. A
structure formed by taking a node N and one or more separate trees and making N the parent of all
roots of the trees is also a tree. This recursive definition enables us to construct trees from existing
trees. After the construction, the new tree would contain the old trees as subtrees. A subtree is a tree
by itself. By definition, the empty tree can also be considered a subtree of every tree.
All nodes with the same parent are called siblings. The level of a node is the number of edges it
takes to reach that particular node from the root. For example, the node in the tree above containing J
is at level 2. The height of a tree is the level of the node furthest away from its root. These definitions
are summarized with a different tree where the letters A through I represent the elements.
root
internal nodes
leaves
height
level of root
level of node with F
nodes at level 1
parent of G, H and I
children of B
A
ABD
EFCGHI
3
0
2
3
D
EF
Binary Trees
225
A binary tree is a tree where each node has exactly two binary trees, commonly referred to as the left
child and right child. Both the left or right trees are also binary trees. They could be empty trees. When
both children are empty trees, the node is considered a leaf. Under good circumstances, binary trees
have the property that you can reach any node in the tree within log 2n steps, where n is the number of
nodes in the tree.
Expression Tree
An expression tree is a binary tree can be used to store an arithmetic expression. The tree can then be
traversed to evaluate the expression. The following expression is represented as a binary tree with
operands as the leaves and operators as internal nodes.
(1 + (5 + (2 * 3))) / 3
Depending on how you want to traverse this tree visit each node once you could come up with
different orderings of the same expression: infix, prefix, or postfix. These tree traversal algorithms are
presented later in this chapter.
226
Chapter 11
The left child of the root (referenced by A) has a value (5) that is less than the value of the root (8).
Likewise, the value of the right child of the root has a value (10) that is greater than the roots value
(8). Also, all the values in the subtree referenced by A (4, 5, 7), are less than the value in the root (8).
To find the node with the value 10 in a binary search tree, the search begins at the root. If the search
value (10) is greater than the element in the root node, search the binary search tree to the right. Since
the right tree has the value you are looking for, the search is successful. If the key is further down the
tree, the search keeps going left or right until the key is found or the subtree is empty indicating the
key was not in the BST. Searching a binary search tree can be O(log n) since half the nodes are
removed from the search at each comparison. Binary search trees store large amounts of real world
data because of their fast searching, insertions, and removal capabilities. The binary search tree will be
explored later in this chapter.
Huffman Tree
David Huffman designed one of the first compression algorithms in 1952. In general, the more
frequently occurring symbols have the shorter encodings. Huffman coding is an integral part of the
standards for high definition television (HDTV). The same approach to have the most frequently
occurring characters in a text file be represented by shorter codes, allows a file to be compressed to
consume less disk space and to take less time to arrive over the Internet.
Part of the compression algorithm involves creation of a Huffman tree that stores all characters in
the file as leaves in a tree. The most frequently occurring letters will have the shortest paths n the
binary tree. The least occurring characters will have longer paths. For example, assuming a text file
contains only the characters 'a', 'e', 'h', 'r', 't', and '_', the Huffman tree could look like this assuming
that 'a', 'e', and '_' occur more frequently than 'h' and 'r'.
Binary Trees
227
'_'
'a'
'e'
't'
'h'
'r'
With the convention that 0 means go left and 1 right, the 6 letters have the following codes:
'a'01
'_'10
'e'11
't'000
'h'0010
'r'0011
Instead of storing 8 bits for each character, the most frequently occurring letters uin this example use
only 2 or 3 bits. Some of the characters in a typical file would have codes for some characters that are
much longer than 8 bits. These 31 bits represent a text file containing the test "tea_at_three".
0001101100100010000001000111111
||||||||||||
tea_at_three
Assuming 8 bit ASCII characters, these 31 bits would require 12*8 or 96 bits.
null
null
M
2
D
4
Chapter 11
228
Notice that some nodes are not used. These unused array locations show the "holes" in the tree. For
example, nodes at indexes 3 and 7 do not appear in the tree and thus have the null value in the array.
In order to find any left or right child for a node, all that is needed is the nodes index. For instance to
find node 2s left and right children, use the following formula:
Left Childs Index
Right Childs Index
=
=
2 * Parents Index + 1
2 * Parents Index + 2
So in this case, node 2s left and right children have indexes of 5 and 6 respectively. Another benefit of
using an array is that you can quickly find a nodes parent with this formula:
Parents Index = (Childs Index 1) / 2
For example, (5-1)/2 and (6-1)/2 both have the same parent in index 2. This works, because with
integer division, 4/2 equals 5/2.
Linked Implementation
Binary trees are often implemented as a linked structure. Whereas nodes in a singly linked structure
had one reference field to refer to the successor element, a TreeNode will have two references one
to the left child and one to the right child. A tree is a collection of nodes with a particular node chosen
as the root. Assume the TreeNode class will be an inner class with private instance variables that store
these three fields
1. a reference to the element
2. a reference to a left tree (another TreeNode),
3. a reference to a right tree (another TreeNode).
To keep things simple, the TreeNode class begins like this so it can store only strings. There are no
generics (the BinarySearchTree class discussed later will use generics).
private class TreeNode {
private String data;
private TreeNode left;
private TreeNode right;
public TreeNode(String elementReference) {
data = elementReference;
left = null;
right = null;
}
}
These three lines of code (if in the same class as this inner node class) will generate the binary tree
structure shown:
TreeNode root = new TreeNode("T");
Binary Trees
229
root
"T"
"L"
"R"
Chapter 11
230
Self-Check
11-1 Using the tree shown below, identify
a) the root
c) the leaves
b) size
d) the internal nodes
11-2 Using the TreeNode class above, write the code that generates this tree.
theRootValue
able
baker
delta
charlie
echo
foxtrot
Like the node classes of previous collections, this TreeNode class can also be placed inside another.
However, instead of a collection class with an insert method, hardCodeATree will be used here to
create a small binary tree. This will be the tree used to present several binary tree algorithms such as
tree traversals in the next section.
// This simple class stores a collection of strings in a binary tree.
// There is no add or insert method. Instead a tree must be "hard coded" to
// demonstrate algorithms such as tree traversals, makeMirror, and height
public class BinaryTreeOfStrings {
private class TreeNode {
private String data;
private TreeNode left;
private TreeNode right;
public TreeNode(String elementReference) {
data = elementReference;
left = null;
right = null;
}
}
// The entry point into the tree
private TreeNode root;
public BinaryTreeOfStrings() {
root = null;
}
Binary Trees
231
root
"C"
"F"
"T"
"B"
"G"
"K"
"R"
Preorder traversal: Visit the root, preorder traverse the left tree, preorder traverse the right subtree
Inorder traversal: Inorder traverse the left subtree, visit the root, inorder traverse the right subtree
Postorder traversal: Postorder traverse the left subtree, postorder traverse the right subtree, visit
the root
When a tree is traversed in a preorder fashion, the parent is processed before its children the left
and right subtrees.
Chapter 11
232
When a binary tree is traversed in a preorder fashion, the root of the tree is "visited" before its children
its left and right subtrees. For example, when preorderPrint is called with the argument root,
the element C would first be visited. Then a call is made to do a preorder traversal beginning at the left
subtree. After the left subtree has been traversed, the algorithm traverses the right subtree of the root
node making the element G the last one visited during this preorder traversal.
root
"C"
"F"
"T"
"B"
"G"
"K"
"R"
The following method performs a preorder traversal over the tree built in the hardCodeATree
method above that has the string "C" in the root node. Writing a solution to this method without
recursion would require a stack and a loop. This algorithm is simpler to write with recursion.
public void preOrderPrint() {
preOrderPrint(root);
}
private void preOrderPrint(TreeNode tree) {
if (tree != null) {
// Visit the root
System.out.print(tree.data + " ");
// Traverse the left subtree
preOrderPrint(tree.left);
// Traverse the right subtree
preOrderPrint(tree.right);
}
}
When the public method calls preOrderPrint passing the reference to the root of the tree, the node
with C is first visited. Next, a recursive call passes a reference to the left subtree with F at the root.
Since this TreeNode argument it is not null, F is visited next and is printed.
Binary Trees
233
root
"C"
"F"
"T"
"G"
"K"
"B"
"R"
"G"
"K"
"R"
Chapter 11
234
root
"C"
"F"
"T"
"B"
"G"
"K"
"R"
Inorder Traversal
During an inorder traversal, each parent gets processed between the processing of its left and right
children. The algorithm changes slightly.
1. Traverse the nodes in the left subtree inorder
2. Process the root
3. Traverse the nodes in the right subtree inorder
Inorder traversal visits the root of each tree only after its left subtree has been traversed inorder. The
right subtree is traversed inorder after the root.
public void inOrderPrint() {
inOrderPrint(root);
}
public void inOrderPrint(TreeNode t) {
if (t != null) {
inOrderPrint(t.left);
System.out.print(t.data + " ");
inOrderPrint(t.right);
}
}
Now a call to inOrderPrint would print out the values of the following tree as
BTRFKCG
Binary Trees
235
root
"C"
"F"
"T"
"G"
"K"
"B"
"R"
The inOrderPrint method keeps calling inOrderPrint recursively with the left subtree. When the
left subtree is finally empty, t.left==null, the block of three statements executed for B.
Postorder Traversal
In a postorder traversal, the root node is processed after the left and right subtrees. The algorithm
shows the process step after the two recursive calls.
1. Traverse the nodes in the left subtree in a postorder manner
2. Traverse the nodes in the right subtree in a postorder manner
3. Process the root
A postorder order traversal would visit the nodes of the same tree in the following fashion:
BRTKFGC
root
"C"
"F"
"T"
"B"
"G"
"K"
"R"
The toString method of linear structures, such as lists, is straightforward. Create one big string from
the first element to the last. A toString method of a tree could be implemented to return the elements
concatenated in pre-, in-, or post-order fashion. A more insightful method would be to print the tree to
show levels with the root at the leftmost (this only works on trees that are not too big). A tree can be
printed sideways with a reverse inorder traversal. Visit the right, the root, and then the left.
Chapter 11
236
G
C
K
F
R
T
B
The printSideways method below does just this To show the different levels, the additional
parameter depth begins at 0 to print a specific number of blank spaces depth times before each
element is printed. When the root is to be printed depth is 0 and no blanks are printed.
public void printSideways() {
printSideways(root, 0);
}
private void printSideways(TreeNode t, int depth) {
if (t != null) {
printSideways(t.right, depth + 1);
for (int j = 1; j <= depth; j++)
System.out.print("
");
System.out.println(t.data);
printSideways(t.left, depth + 1);
}
}
Self-Check
11-3 Write out the values of each node of this tree as the tree is traversed both
a. inOrder
b. preorder
c. postOrder
11-4 Implement the private helper method postOrderPrint that will print all elements separated by a
space when this public method is called:
public void postOrderPrint() {
postOrderPrint(root);
}
Binary Trees
237
height
The height of an empty tree is -1, the height of an empty tree is 0, and the height of a tree of size
greater than 1 is the longer path in either the left subtree or right subtree from the root. The method
considers the base case first to return -1 if the tree is empty. When there is one node, height returns
1 + the maximum height of the left or right trees. Since both are empty, Math.max returns -1 and the
final result is (1 + -1) or 0.
public int height() {
return height(root);
}
private int height(TreeNode t) {
if (t == null)
return -1;
else
return 1 + Math.max(height(t.left), height(t.right));
}
For larger trees, height returns the larger of the height of the left subtree or the height of the
right subtree.
leafs
Traversal algorithms allow all nodes in a binary tree to be visited. So the same pattern can be used to
search for elements, send messages to all elements, or count the number of nodes. In these situations,
the entire binary tree will be traversed.
The following methods return the number of leafs in a tree When a leaf is found, the method
returns 1 + all leafs to the left + all leafs to the right. If t references an internal node (not a leaf), the
recursive calls to the left and right must still be made to search further down the tree for leafs.
public int leafs() {
return leafs(root);
}
private int leafs(TreeNode t) {
if (t == null)
return 0;
else {
int result = 0;
if (t.left == null && t.right == null)
result = 1;
return result + leafs(t.left) + leafs(t.right);
}
}
Chapter 11
238
findMin
The findMin method returns the string that precedes all others alphabetically. It uses a preorder
traversal to visit the root nodes first ( findMin could also use be a postorder or inorder traversal). This
example show that it may be easier to understand or implement a binary tree algorithm that has an
instance variable initialized in the public method and adjusted in the private helper method.
private String min;
public String findMin() {
if (root == null)
return null;
else {
min = root.data;
findMinHelper(root);
return min;
}
}
public void findMinHelper(TreeNode t) {
if (t != null) {
String temp = t.data;
if (temp.compareTo(min) < 0)
min = temp;
findMinHelper(t.left);
findMinHelper(t.right);
}
}
Self-Check
11-5
To BinaryTreeOfStrings, add method findMax that returns the string that follows all others
alphabetically.
11-6
To BinaryTreeOfStrings, add method size that returns the number of nodes in the tree.
11-7
11-8
To BinaryTreeOfStrings, add method method isFull that returns true if the binary tree is
full or false if it is not. A full tree is a binary tree in which each node has exactly zero or two
children.
Binary Trees
11.5
239
A Binary Search Tree is a binary tree with an ordering property that allows O(log n) retrieval,
insertion, and removal of individual elements. Defined recursively, a binary search tree is
an empty tree, or
consists of a node called the root, and two children, left and right, each of which are themselves
binary search trees. Each node contains data at the root that is greater than all values in the left
subtree while also being less than all values in the right subtree. No two nodes compare equally.
This is called the binary search tree ordering property.
The following two trees represent a binary search tree and a binary tree respectively. Both have the
same structure every node has two children (which may be empty trees shown with /). Only the
first tree has the binary search ordering property. The second does not have the BST ordering property.
The node containing 55 is found in the left subtree of 50 instead of the right subtree.
A Binary Search Tree
50
root
25
-12
75
36
57
52
90
61
A Binary Tree that does not have the ordering property (55 in wrong place)
50
root
Not
less
than 50
25
-12
75
55
57
52
90
61
240
Chapter 11
Java generics will make this collection class more type safe. It would be tempting to use this familiar
class heading.
public class BinarySearchTree<E>
This class heading uses a bounded parameter to restrict the types allowed in a BinarySearchTree
to Comparables only. This heading will also avoid the need to cast to Comparable. Using <E
extends Comparable <E>> will also avoid cast exceptions errors at runtime. Instead, an attempt
to compile a construction with a NonComparable assuming NonComparable is a class that does
not implement Comparable results in a more preferable compile time error.
BinarySearchTree<String> strings = new BinarySearchTree<String>();
BinarySearchTree<Integer> integers = new BinarySearchTree<Integer>();
BinarySearchTree<NonComparable> no = new BinarySearchTree<NonComparable>();
Bound mismatch: The type NonComparable is not a valid substitute for the bounded parameter <E
extends Comparable<E>>
So far, most elements have been String or Integer objects. This makes explanations shorter. For
example, it is easier to write stringTree.insert("A"); than accountTree.insert(new
BankAccount("Zeke Nathanielson", 150.00)); (it is also easier for authors to fit short
strings and integers in the boxes that represent elements of a tree).
However, collections of only strings or integers are not all that common outside of textbooks. You
will more likely need to store real-world data. Then the find method seems more appropriate. For
example, you could have a binary search tree that stores BankAccount objects assuming
BankAccount implements Comparable. Then the return value from find could be used to update
the object in the collection, by sending withdraw, deposit, or getBalance messages.
BinarySearchTree<BankAccount> accountCollection =
new BinarySearchTree<BankAccount>();
Binary Trees
241
Chapter 11
242
}
insert
A new node will always be inserted as a leaf. The insert algorithm begins at the root and proceeds as if
it were searching for that element. For example, to insert a new Integer object with the value of 28
into the following binary search tree, 28 will first be compared to 50. Since 28 is less than the root
value of 50, the search proceeds down the left subtree. Since 28 is greater than 25, the search proceeds
to the right subtree. Since 28 is less than 36, the search attempts to proceed left, but stops. The tree to
the left is empty. At this point, the new element should be added to the tree as the left child of the node
with 36.
50
root
25
-12
36
28
75
prev
57
52
90
61
The search to find the insertion point ends under either of these two conditions:
1. A node matching the new value is found.
2. There is no further place to search. The node can then be added as a leaf.
In the first case, the insert method could simply quit without adding the new node (recall that binary
search trees do not allow duplicate elements). If the search stopped due to finding an empty tree, then
a new TreeNode with the integer 28 gets constructed and the reference to this new node replaces one
of the empty trees (the null value) in the leaf last visited. In this case, the reference to the new node
with 28 replaces the empty tree to the left of 36.
One problem to be resolved is that a reference variable (named curr in the code below) used to
find the insertion point eventually becomes null. The algorithm must determine where it should store
the reference to the new node. It will be in either the left link or the right link of the node last visited.
In other words, after the insertion spot is found in the loop, the code must determine if the new
element is greater than or less than its soon to be parent.
Therefore, two reference variables will be used to search through the binary search tree. The
TreeNode reference named prev will keep track of the previous node visited. (Note: There are other
ways to implement this).
The following method is one solution to insertion. It utilizes the Binary Search Tree ordering
property. The algorithm checks that the element about to be inserted is either less than or greater than
each node visited. This allows the appropriate path to be taken. It ensures that the new element will be
Binary Trees
243
inserted into a location that keeps the tree a binary search tree. If the new element to be inserted
compares equally to the object in a node, the insert is abandoned with a return statement.
public boolean insert(E newElement) {
// newElement will be added and this will still be a
// BinarySearchTree. This tree will not insert newElement
// if it will compareTo an existing element equally.
if (root == null)
root = new TreeNode(newElement);
else {
// find the proper leaf to attach to
TreeNode curr = root;
TreeNode prev = root;
while (curr != null) {
prev = curr;
if (newElement.compareTo(curr.data) < 0)
curr = curr.left;
else if (newElement.compareTo(curr.data) > 0)
curr = curr.right;
else {
System.out.println(newElement + " in this BST");
return false;
}
}
// Correct leaf has now been found. Determine whether to
// link the new node came from prev.left or prev.right
if (newElement.compareTo(prev.data) < 0)
prev.left = new TreeNode(newElement);
else
prev.right = new TreeNode(newElement);
}
return true;
} // end insert
When curr finally becomes null, it must be from either prev's left or right.
36
?
prev
28
This situation is handled by the code at the end of insert that compares newElement to prev.data.
find
This BinarySearchTree needed some way to insert elements before find could be tested so insert
could be tested, a bit of illogicality. Both will be tested now with a unit test that begins by inserting a
Chapter 11
244
small set of integer elements. The printSideways message ensures the structure of the tree has the
BST ordering property.
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class BinarySearchTreeTest {
private BinarySearchTree<Integer> aBST;
// Initialize aBST before each test method
@Before
public static void setUpBST() {
aBST = new BinarySearchTree<Integer>();
aBST.insert(50);
aBST.insert(25);
aBST.insert(75);
aBST.insert(-12);
aBST.insert(36);
aBST.insert(57);
aBST.insert(90);
aBST.insert(52);
aBST.insert(61);
aBST.printSideways();
}
// Any @Test in this unit test can use aBST with the same 9 integers
// shown in @Before as seyUpBST will be called before each @Test
}
Output
90
75
61
57
50
root
52
50
25
36
75
25
-12
-12
36
57
52
90
61
The first test method ensures that elements that can be added result in true and those that can't result in
false. Programmers could use this to ensure the element was added or the element already existed.
Binary Trees
245
@Test
public void testInsertDoesNotAddExistingElements() {
assertTrue(aBST.insert(789));
assertTrue(aBST.insert(-789));
assertFalse(aBST.insert(50));
assertFalse(aBST.insert(61));
}
This test method ensures that the integers are found and that the correct value is returned.
@Test
public void testFindWhenInserted() {
assertEquals(50, aBST.find(50));
assertEquals(25, aBST.find(25));
assertEquals(75, aBST.find(75));
assertEquals(-12, aBST.find(-12));
assertEquals(36, aBST.find(36));
assertEquals(57, aBST.find(57));
assertEquals(90, aBST.find(90));
assertEquals(52, aBST.find(52));
assertEquals(61, aBST.find(61));
}
This test method ensures that a few integers not inserted are also not found.
@Test
public void testFindWhenElementsNotInserted() {
assertNull(aBST.find(-999));
assertNull(aBST.find(0));
assertNull(aBST.find(999));
}
The search through the nodes of a aBST begins at the root of the tree. For example, to search for a
node that will compareTo 57 equally, the method first compares 57 to the root element, which has the
value of 50. Since 57 is greater than 50, the search proceeds down the right subtree (recall that nodes
to the right are greater). Then 57 is compared to 75. Since 57 is less than 75, the search proceeds down
the left subtree of 75. Then 57 is compared to the node with 57. Since these compare equally, a
reference to the element is returned to the caller. The binary search continues until one of these two
events occur:
1. The element is found
2. There is an attempt to search an empty tree (nowhere to go -- the node is not in the tree)
In the first case, the reference to the data in the node is returned to the sender. In the second case, the
method returns null to indicate that the element was not in the tree. Here is an implementation of
find method.
// Return a reference to the object that will compareTo
// searchElement equally. Otherwise, return null.
public E find(E searchElement) {
Chapter 11
246
// Begin the search at the root
TreeNode ref = root;
// Search until found or null is reached
while (ref != null) {
if (searchElement.compareTo(ref.data) == 0)
return ref.data; // found
else if (searchElement.compareTo(ref.data) < 0)
ref = ref.left; // go down the left subtree
else
ref = ref.right; // go down the right subtree
}
// Found an empty tree. SearchElement was not found
return null;
}
The following picture shows the changing values of the external reference t as it references the three
different nodes in its search for 57:
50
root
25
-12
75
36
57
52
90
61
One of the reasons that binary search trees are frequently used to store collections is the speed at
which elements can be found. In a manner similar to a binary search algorithm, half of the elements
can be eliminated from the search in a BST at each loop iteration. When you go left from one node,
you ignore all the elements to the right, which is usually about half of the remaining nodes. Assuming
the BinarySearchTree is fairly complete, searching in a binary search tree is O(log n). For
example, in the previous search, t referred to only three nodes in a collection of size 9. A tree with 10
levels could have a maximum size of 1,024 nodes. It could take as few as 10 comparisons to find
something on level 10.
remove
The remove method is left as a programming exercise.
Binary Trees
247
If the element we were searching for was the right-most element in this tree (10), the search time
would be O(n), the same as a singly linked structure.
Thus, it is very important that the tree remain balanced. If values are inserted randomly to a
binary search tree, this condition may be met, and the tree will remain adequately balanced so that
search and insertion time will be O(log n).
The study of trees has been very fruitful because of this problem. There are many variants of trees,
e.g., red-black trees, AVL trees, B-trees, that try to solve this problem. They would do this by rebalancing the tree after operations that unbalance it are performed on them.
This seems inefficient, especially when we have gone to great lengths to implement a binary tree
in order to make the code efficient. Re-balancing a binary tree is a very tedious task and is beyond the
scope of this book. However, it should be noted that having to rebalance a binary tree every now and
then adds overhead to the runtime of a program that requires a binary search tree. But if you are
mostly searching, which is often the case, the balanced tree is the way to go.
The table below compares a binary search trees performance with a sorted array and singly linked
structure (the remove method for BST is left as an exercise).
remove
find
insert
Sorted Array
O(n)
O(log n)
O(n)
Singly Linked
O(n)
O(n)
O(n)
Chapter 11
248
Programming Projects
11A makeMirror
Rearrange the elements of the BinaryTreeOfStrings class shown in this chapter to its mirrored
image. First get the file that is available on this textbook's website.
o
BinaryTreeOfStrings.java
Traversal algorithms have been shown to begin at the root, go down the tree, and use previous
activation records that have references to previously visited nodes. Implement method makeMirror
so it goes to the bottom nodes first and then swaps the left and right links. Then use references from
previous activation records to perform a swap on previously visited nodes. The following code should
generate the output shown using BinaryTreeOfStrings. It is the hardCodedTree in its original form
followed by its mirror image.
BinaryTreeOfStrings bt = new BinaryTreeOfStrings();
bt.hardCodeATree();
bt.printSideways();
bt.makeMirror();
bt.printSideways();
Output
G
C
K
F
R
T
B
B
T
R
F
K
C
G
Binary Trees
249
Details
An empty tree has 0 nodes at all levels >= 0. A tree with one node has 1 node at
level 1 and 0 at all other levels > 0. The following tree has two nodes at level 1, three nodes at
level 2, and one node at level 3.
atDepth
M
/
G
/ \
B K
\
R
/
P
\
W
maxedOut Returns true if the BinarySearchTree has exactly the maximum number of nodes at
every level in the tree. The following trees have all levels with the maximum number of nodes:
M
emptyTree
/
G
/ \
B K
/ \
R
/ \
P W
These binary trees do not have the maximum number of elements at all levels:
M
/
G
/ \
B K
M
/ \
\
R
G
/ \
C
M
/ \
\
R
K
/ \
A
B
G
/ \
B K
R
\
W
250
Chapter 11
remove Use the following algorithm to remove node from a BST or another algorithm that you
derive. You should build up a unit test as you test for all four cases given in the algorithm below from
the simplest (Case 1) to the most difficult (Case 4 when curr has a left child). The method must have
this exact method heading:
private boolean remove(E elementToRemove) {
Remove Algorithm
Traverse the tree t until elementToRemove is found. Use curr and prev to find the element
leaving curr referring the node you want to remove. It may be the case that curr is null indicating
that elementToRemove was not found. There are four cases to consider :
Case 1: Not found
return result (false)
Case 2: The root is to be removed, but it has no left child.
root = root's right subtree (assuming root refers the the root node in your BST)
Case 3: The node is further down tree with no left child. Now must adjust one of the parent's links
if curr is on the left side of prev,
move parent's left link down to down to curr's right child
else
curr is on the right side of prev, so move parent's right down to down to curr's right child
Case 4: curr has a left child.
We can no longer ignore the left subtree. So find the maximum key in the left subtree and
move that key/value pair to the node referenced by curr. Then eliminate the maximum in the
left subtree, which is now being referenced from the original node to remove.
Binary Trees
251