CH 08
CH 08
Trees
Zimran
Ishbak
Ishmael
Jokshan
Midian
Shuah
Medan
Isaac
Dedan
Epher
Ephah
Hanoch
Hadad
Kedemah
Nebaioth
Sheba
Adbeel
Mishma
Tema
Abida
Jacob (Israel)
Kedar
Mibsam
Naphish
Dumah
Esau
Eldaah
Massa
Jetur
Reuben
Zebulun
Benjamin
Naphtali
Levi
Eliphaz
Korah
Asher
Jeush
Simeon
Judah
Dan
Gad
Issachar
Dinah
Joseph
Reuel
Jalam
www.it-ebooks.info
8.1. General Trees 309
Electronics R’Us
Figure 8.2: A tree with 17 nodes representing the organization of a fictitious cor-
poration. The root stores Electronics R’Us. The children of the root store R&D,
Sales, Purchasing, and Manufacturing. The internal nodes store Sales, Interna-
tional, Overseas, Electronics R’Us, and Manufacturing.
www.it-ebooks.info
310 Chapter 8. Trees
Other Node Relationships
Two nodes that are children of the same parent are siblings. A node v is external
if v has no children. A node v is internal if it has one or more children. External
nodes are also known as leaves.
Example 8.1: In Section 5.1.4, we discussed the hierarchical relationship be-
tween files and directories in a computer’s file system, although at the time we
did not emphasize the nomenclature of a file system as a tree. In Figure 8.3, we
revisit an earlier example. We see that the internal nodes of the tree are associ-
ated with directories and the leaves are associated with regular files. In the Unix
and Linux operating systems, the root of the tree is appropriately called the “root
directory,” and is represented by the symbol “/.”
/user/rt/courses/
cs016/ cs252/
grades grades
homeworks/ programs/ projects/
www.it-ebooks.info
8.1. General Trees 311
Ordered Trees
A tree is ordered if there is a meaningful linear order among the children of each
node; that is, we purposefully identify the children of a node as being the first,
second, third, and so on. Such an order is usually visualized by arranging siblings
left to right, according to their order.
Example 8.2: The components of a structured document, such as a book, are hier-
archically organized as a tree whose internal nodes are parts, chapters, and sections,
and whose leaves are paragraphs, tables, figures, and so on. (See Figure 8.4.) The
root of the tree corresponds to the book itself. We could, in fact, consider expanding
the tree further to show paragraphs consisting of sentences, sentences consisting of
words, and words consisting of characters. Such a tree is an example of an ordered
tree, because there is a well-defined order among the children of each node.
Book
§ 1.1 ... § 1.4 § 5.1 ... § 5.7 § 6.1 ... § 6.5 § 9.1 ... § 9.6
Let’s look back at the other examples of trees that we have described thus far,
and consider whether the order of children is significant. A family tree that de-
scribes generational relationships, as in Figure 8.1, is often modeled as an ordered
tree, with siblings ordered according to their birth.
In contrast, an organizational chart for a company, as in Figure 8.2, is typically
considered an unordered tree. Likewise, when using a tree to describe an inher-
itance hierarchy, as in Figure 2.7, there is no particular significance to the order
among the subclasses of a parent class. Finally, we consider the use of a tree in
modeling a computer’s file system, as in Figure 8.3. Although an operating system
often displays entries of a directory in a particular order (e.g., alphabetical, chrono-
logical), such an order is not typically inherent to the file system’s representation.
www.it-ebooks.info
312 Chapter 8. Trees
www.it-ebooks.info
8.1. General Trees 313
1 /∗∗ An interface for a tree where nodes can have an arbitrary number of children. ∗/
2 public interface Tree<E> extends Iterable<E> {
3 Position<E> root( );
4 Position<E> parent(Position<E> p) throws IllegalArgumentException;
5 Iterable<Position<E>> children(Position<E> p)
6 throws IllegalArgumentException;
7 int numChildren(Position<E> p) throws IllegalArgumentException;
8 boolean isInternal(Position<E> p) throws IllegalArgumentException;
9 boolean isExternal(Position<E> p) throws IllegalArgumentException;
10 boolean isRoot(Position<E> p) throws IllegalArgumentException;
11 int size( );
12 boolean isEmpty( );
13 Iterator<E> iterator( );
14 Iterable<Position<E>> positions( );
15 }
Code Fragment 8.1: Definition of the Tree interface.
www.it-ebooks.info
314 Chapter 8. Trees
In the case of our Tree interface, we will define an AbstractTree base class,
demonstrating how many tree-based algorithms can be described independently of
the low-level representation of a tree data structure. In fact, if a concrete implemen-
tation provides three fundamental methods—root( ), parent(p), and children(p)—
all other behaviors of the Tree interface can be derived within the AbstractTree
base class.
Code Fragment 8.2 presents an initial implementation of an AbstractTree base
class that provides the most trivial methods of the Tree interface. We will defer
until Section 8.4 a discussion of general tree-traversal algorithms that can be used
to produced the positions( ) iteration within the AbstractTree class. As with our
positional list ADT in Chapter 7, the iteration of positions of the tree can easily be
adapted to produce an iteration of the elements of a tree, or even to determine the
size of a tree (although our concrete tree implementations will provide more direct
means for reporting the size).
1 /∗∗ An abstract base class providing some functionality of the Tree interface. ∗/
2 public abstract class AbstractTree<E> implements Tree<E> {
3 public boolean isInternal(Position<E> p) { return numChildren(p) > 0; }
4 public boolean isExternal(Position<E> p) { return numChildren(p) == 0; }
5 public boolean isRoot(Position<E> p) { return p == root( ); }
6 public boolean isEmpty( ) { return size( ) == 0; }
7 }
Code Fragment 8.2: An initial implementation of the AbstractTree base class. (We
add additional functionality to this class as the chapter continues.)
www.it-ebooks.info
8.1. General Trees 315
1 /∗∗ Returns the number of levels separating Position p from the root. ∗/
2 public int depth(Position<E> p) {
3 if (isRoot(p))
4 return 0;
5 else
6 return 1 + depth(parent(p));
7 }
Code Fragment 8.3: Method depth, as implemented within the AbstractTree class.
Height
We next define the height of a tree to be equal to the maximum of the depths of
its positions (or zero, if the tree is empty). For example, the tree of Figure 8.2 has
height 4, as the node storing Africa (and its siblings) has depth 4. It is easy to see
that the position with maximum depth must be a leaf.
In Code Fragment 8.4, we present a method that computes the height of a tree
based on this definition. Unfortunately, such an approach is not very efficient,
and so name the algorithm heightBad and declare it as a private method of the
AbstractTree class (so that it cannot be used by others).
1 /∗∗ Returns the height of the tree. ∗/
2 private int heightBad( ) { // works, but quadratic worst-case time
3 int h = 0;
4 for (Position<E> p : positions( ))
5 if (isExternal(p)) // only consider leaf positions
6 h = Math.max(h, depth(p));
7 return h;
8 }
Code Fragment 8.4: Method heightBad of the AbstractTree class. Note that this
method calls the depth method from Code Fragment 8.3.
Although we have not yet defined the positions( ) method, we will see that it
can be implemented such that the entire iteration runs in O(n) time, where n is
the number of positions of T . Because heightBad calls algorithm depth(p) on
each leaf of T , its running time is O(n + ∑ p∈L (d p + 1)), where L is the set of leaf
positions of T . In the worst case, the sum ∑ p∈L (d p + 1) is proportional to n2 . (See
Exercise C-8.31.) Thus, algorithm heightBad runs in O(n2 ) worst-case time.
We can compute the height of a tree more efficiently, in O(n) worst-case time,
by considering a recursive definition. To do this, we will parameterize a function
based on a position within the tree, and calculate the height of the subtree rooted at
that position. Formally, we define the height of a position p in a tree T as follows:
• If p is a leaf, then the height of p is 0.
• Otherwise, the height of p is one more than the maximum of the heights of
p’s children.
www.it-ebooks.info
316 Chapter 8. Trees
The following proposition relates our original definition of the height of a tree
to the height of the root position using this recursive formula.
Proposition 8.3: The height of the root of a nonempty tree T , according to the
recursive definition, equals the maximum depth among all leaves of tree T .
We leave the justification of this proposition as Exercise R-8.3.
An implementation of a recursive algorithm to compute the height of a subtree
rooted at a given position p is presented in Code Fragment 8.5. The overall height
of a nonempty tree can be computed by sending the root of the tree as a parameter.
1 /∗∗ Returns the height of the subtree rooted at Position p. ∗/
2 public int height(Position<E> p) {
3 int h = 0; // base case if p is external
4 for (Position<E> c : children(p))
5 h = Math.max(h, 1 + height(c));
6 return h;
7 }
Code Fragment 8.5: Method height for computing the height of a subtree rooted at
a position p of an AbstractTree.
www.it-ebooks.info
8.2. Binary Trees 317
Yes No
Yes No
Yes No
www.it-ebooks.info
318 Chapter 8. Trees
Example 8.6: An arithmetic expression can be represented by a binary tree whose
leaves are associated with variables or constants, and whose internal nodes are
associated with one of the operators +, −, ∗, and /, as demonstrated in Figure 8.6.
Each node in such a tree has a value associated with it.
• If a node is leaf, then its value is that of its variable or constant.
• If a node is internal, then its value is defined by applying its operation to the
values of its children.
A typical arithmetic expression tree is a proper binary tree, since each operator
+, −, ∗, and / takes exactly two operands. Of course, if we were to allow unary
operators, like negation (−), as in “−x,” then we could have an improper binary
tree.
/ +
∗ + ∗ 6
+ 3 − 2 3 −
3 1 9 5 7 4
Figure 8.6: A binary tree representing an arithmetic expression. This tree repre-
sents the expression ((((3 + 1) ∗ 3)/((9 − 5) + 2)) − ((3 ∗ (7 − 4)) + 6)). The value
associated with the internal node labeled “/” is 2.
www.it-ebooks.info
8.2. Binary Trees 319
www.it-ebooks.info
320 Chapter 8. Trees
The new sibling method is derived from a combination of left, right, and parent.
Typically, we identify the sibling of a position p as the “other” child of p’s parent.
However, p does not have a sibling if it is the root, or if it is the only child of its
parent.
We can also use the presumed left and right methods to provide concrete im-
plementations of the numChildren and children methods, which are part of the
original Tree interface. Using the terminology of Section 7.4, the implementa-
tion of the children method relies on producing a snapshot. We create an empty
java.util.ArrayList, which qualifies as being an iterable container, and then add any
children that exist, ordered so that a left child is reported before a right child.
1 /∗∗ An abstract base class providing some functionality of the BinaryTree interface.∗/
2 public abstract class AbstractBinaryTree<E> extends AbstractTree<E>
3 implements BinaryTree<E> {
4 /∗∗ Returns the Position of p's sibling (or null if no sibling exists). ∗/
5 public Position<E> sibling(Position<E> p) {
6 Position<E> parent = parent(p);
7 if (parent == null) return null; // p must be the root
8 if (p == left(parent)) // p is a left child
9 return right(parent); // (right child might be null)
10 else // p is a right child
11 return left(parent); // (left child might be null)
12 }
13 /∗∗ Returns the number of children of Position p. ∗/
14 public int numChildren(Position<E> p) {
15 int count=0;
16 if (left(p) != null)
17 count++;
18 if (right(p) != null)
19 count++;
20 return count;
21 }
22 /∗∗ Returns an iterable collection of the Positions representing p's children. ∗/
23 public Iterable<Position<E>> children(Position<E> p) {
24 List<Position<E>> snapshot = new ArrayList<>(2); // max capacity of 2
25 if (left(p) != null)
26 snapshot.add(left(p));
27 if (right(p) != null)
28 snapshot.add(right(p));
29 return snapshot;
30 }
31 }
Code Fragment 8.7: An AbstractBinaryTree class that extends the AbstractTree
class of Code Fragment 8.2 and implements the BinaryTree interface of Code Frag-
ment 8.6.
www.it-ebooks.info
8.2. Binary Trees 321
0 1
1 2
2 4
3 8
... ...
...
...
We can see that the maximum number of nodes on the levels of a binary tree
grows exponentially as we go down the tree. From this simple observation, we can
derive the following properties relating the height of a binary tree T with its number
of nodes. A detailed justification of these properties is left as Exercise R-8.8.
Proposition 8.7: Let T be a nonempty binary tree, and let n, nE , nI , and h denote
the number of nodes, number of external nodes, number of internal nodes, and
height of T , respectively. Then T has the following properties:
1. h + 1 ≤ n ≤ 2h+1 − 1
2. 1 ≤ nE ≤ 2h
3. h ≤ nI ≤ 2h − 1
4. log(n + 1) − 1 ≤ h ≤ n − 1
Also, if T is proper, then T has the following properties:
1. 2h + 1 ≤ n ≤ 2h+1 − 1
2. h + 1 ≤ nE ≤ 2h
3. h ≤ nI ≤ 2h − 1
4. log(n + 1) − 1 ≤ h ≤ (n − 1)/2
www.it-ebooks.info
322 Chapter 8. Trees
Relating Internal Nodes to External Nodes in a Proper Binary Tree
In addition to the earlier binary tree properties, the following relationship exists
between the number of internal nodes and external nodes in a proper binary tree.
Proposition 8.8: In a nonempty proper binary tree T , with nE external nodes and
nI internal nodes, we have nE = nI + 1.
Justification: We justify this proposition by removing nodes from T and divid-
ing them up into two “piles,” an internal-node pile and an external-node pile, until
T becomes empty. The piles are initially empty. By the end, we will show that the
external-node pile has one more node than the internal-node pile. We consider two
cases:
Case 1: If T has only one node v, we remove v and place it on the external-node
pile. Thus, the external-node pile has one node and the internal-node pile is
empty.
Case 2: Otherwise (T has more than one node), we remove from T an (arbitrary)
external node w and its parent v, which is an internal node. We place w on
the external-node pile and v on the internal-node pile. If v has a parent u,
then we reconnect u with the former sibling z of w, as shown in Figure 8.8.
This operation, removes one internal node and one external node, and leaves
the tree being a proper binary tree.
Repeating this operation, we eventually are left with a final tree consisting
of a single node. Note that the same number of external and internal nodes
have been removed and placed on their respective piles by the sequence of
operations leading to this final tree. Now, we remove the node of the final
tree and we place it on the external-node pile. Thus, the external-node pile
has one more node than the internal-node pile.
u u
v u
z w z z
Figure 8.8: Operation that removes an external node and its parent node, used in
the justification of Proposition 8.8.
Note that the above relationship does not hold, in general, for improper binary
trees and nonbinary trees, although there are other interesting relationships that do
hold. (See Exercises C-8.30 through C-8.32.)
www.it-ebooks.info
8.3. Implementing Trees 323
∅
root
5
size
parent
∅ ∅
left right
element
∅ ∅ ∅ ∅
(a) (b)
Figure 8.9: A linked structure for representing: (a) a single node; (b) a binary tree.
www.it-ebooks.info
324 Chapter 8. Trees
Operations for Updating a Linked Binary Tree
The Tree and BinaryTree interfaces define a variety of methods for inspecting an
existing tree, yet they do not declare any update methods. Presuming that a newly
constructed tree is empty, we would like to have means for changing the structure
of content of a tree.
Although the principle of encapsulation suggests that the outward behaviors of
an abstract data type need not depend on the internal representation, the efficiency of
the operations depends greatly upon the representation. We therefore prefer to have
each concrete implementation of a tree class support the most suitable behaviors for
updating a tree. In the case of a linked binary tree, we suggest that the following
update methods be supported:
www.it-ebooks.info
8.3. Implementing Trees 325
Java Implementation of a Linked Binary Tree Structure
• Code Fragment 8.8 contains the definition of the nested Node class, which
implements the Position interface. It also defines a method, createNode,
that returns a new node instance. Such a design uses what is known as the
factory method pattern, allowing us to later subclass our tree in order to use
a specialized node type. (See Section 11.2.1.) Code Fragment 8.8 concludes
with the declaration of the instance variables of the outer LinkedBinaryTree
class and its constructor.
• Code Fragment 8.9 includes the protected validate(p) method, followed by
the accessors size, root, left, and right. We note that all other methods of the
Tree and BinaryTree interfaces are derived from these four concrete methods,
via the AbstractTree and AbstractBinaryTree base classes.
• Code Fragments 8.10 and 8.11 provide the six update methods for a linked
binary tree, as described on the preceding page. We note that the three
methods—addRoot, addLeft, and addRight—each rely on use of the factory
method, createNode, to produce a new node instance.
The remove method, given at the end of Code Fragment 8.11, intentionally
sets the parent field of a deleted node to refer to itself, in accordance with our
conventional representation of a defunct node (as detected within the validate
method). It resets all other fields to null, to aid in garbage collection.
www.it-ebooks.info
326 Chapter 8. Trees
www.it-ebooks.info
8.3. Implementing Trees 327
41 // nonpublic utility
42 /∗∗ Validates the position and returns it as a node. ∗/
43 protected Node<E> validate(Position<E> p) throws IllegalArgumentException {
44 if (!(p instanceof Node))
45 throw new IllegalArgumentException("Not valid position type");
46 Node<E> node = (Node<E>) p; // safe cast
47 if (node.getParent( ) == node) // our convention for defunct node
48 throw new IllegalArgumentException("p is no longer in the tree");
49 return node;
50 }
51
52 // accessor methods (not already implemented in AbstractBinaryTree)
53 /∗∗ Returns the number of nodes in the tree. ∗/
54 public int size( ) {
55 return size;
56 }
57
58 /∗∗ Returns the root Position of the tree (or null if tree is empty). ∗/
59 public Position<E> root( ) {
60 return root;
61 }
62
63 /∗∗ Returns the Position of p's parent (or null if p is root). ∗/
64 public Position<E> parent(Position<E> p) throws IllegalArgumentException {
65 Node<E> node = validate(p);
66 return node.getParent( );
67 }
68
69 /∗∗ Returns the Position of p's left child (or null if no child exists). ∗/
70 public Position<E> left(Position<E> p) throws IllegalArgumentException {
71 Node<E> node = validate(p);
72 return node.getLeft( );
73 }
74
75 /∗∗ Returns the Position of p's right child (or null if no child exists). ∗/
76 public Position<E> right(Position<E> p) throws IllegalArgumentException {
77 Node<E> node = validate(p);
78 return node.getRight( );
79 }
Code Fragment 8.9: An implementation of the LinkedBinaryTree class.
(Continued from Code Fragment 8.8; continues in Code Fragments 8.10 and 8.11.)
www.it-ebooks.info
328 Chapter 8. Trees
www.it-ebooks.info
8.3. Implementing Trees 329
120 /∗∗ Attaches trees t1 and t2 as left and right subtrees of external p. ∗/
121 public void attach(Position<E> p, LinkedBinaryTree<E> t1,
122 LinkedBinaryTree<E> t2) throws IllegalArgumentException {
123 Node<E> node = validate(p);
124 if (isInternal(p)) throw new IllegalArgumentException("p must be a leaf");
125 size += t1.size( ) + t2.size( );
126 if (!t1.isEmpty( )) { // attach t1 as left subtree of node
127 t1.root.setParent(node);
128 node.setLeft(t1.root);
129 t1.root = null;
130 t1.size = 0;
131 }
132 if (!t2.isEmpty( )) { // attach t2 as right subtree of node
133 t2.root.setParent(node);
134 node.setRight(t2.root);
135 t2.root = null;
136 t2.size = 0;
137 }
138 }
139 /∗∗ Removes the node at Position p and replaces it with its child, if any. ∗/
140 public E remove(Position<E> p) throws IllegalArgumentException {
141 Node<E> node = validate(p);
142 if (numChildren(p) == 2)
143 throw new IllegalArgumentException("p has two children");
144 Node<E> child = (node.getLeft( ) != null ? node.getLeft( ) : node.getRight( ) );
145 if (child != null)
146 child.setParent(node.getParent( )); // child’s grandparent becomes its parent
147 if (node == root)
148 root = child; // child becomes root
149 else {
150 Node<E> parent = node.getParent( );
151 if (node == parent.getLeft( ))
152 parent.setLeft(child);
153 else
154 parent.setRight(child);
155 }
156 size−−;
157 E temp = node.getElement( );
158 node.setElement(null); // help garbage collection
159 node.setLeft(null);
160 node.setRight(null);
161 node.setParent(node); // our convention for defunct node
162 return temp;
163 }
164 } //----------- end of LinkedBinaryTree class -----------
Code Fragment 8.11: An implementation of the LinkedBinaryTree class.
(Continued from Code Fragments 8.8–8.10.)
www.it-ebooks.info
330 Chapter 8. Trees
Performance of the Linked Binary Tree Implementation
To summarize the efficiencies of the linked structure representation, we analyze the
running times of the LinkedBinaryTree methods, including derived methods that
are inherited from the AbstractTree and AbstractBinaryTree classes:
• The size method, implemented in LinkedBinaryTree, uses an instance vari-
able storing the number of nodes of a tree and therefore takes O(1) time.
Method isEmpty, inherited from AbstractTree, relies on a single call to size
and thus takes O(1) time.
• The accessor methods root, left, right, and parent are implemented directly
in LinkedBinaryTree and take O(1) time each. The sibling, children, and
numChildren methods are derived in AbstractBinaryTree using on a constant
number of calls to these other accessors, so they run in O(1) time as well.
• The isInternal and isExternal methods, inherited from the AbstractTree class,
rely on a call to numChildren, and thus run in O(1) time as well. The isRoot
method, also implemented in AbstractTree, relies on a comparison to the
result of the root method and runs in O(1) time.
• The update method, set, clearly runs in O(1) time. More significantly, all of
the methods addRoot, addLeft, addRight, attach, and remove run in O(1)
time, as each involves relinking only a constant number of parent-child rela-
tionships per operation.
• Methods depth and height were each analyzed in Section 8.1.3. The depth
method at position p runs in O(d p + 1) time where d p is its depth; the height
method on the root of the tree runs in O(n) time.
The overall space requirement of this data structure is O(n), for a tree with
n nodes, as there is an instance of the Node class for every node, in addition to the
top-level size and root fields. Table 8.1 summarizes the performance of the linked
structure implementation of a binary tree.
Table 8.1: Running times for the methods of an n-node binary tree implemented
with a linked structure. The space usage is O(n).
www.it-ebooks.info
8.3. Implementing Trees 331
1 2
(a)
3 4 5 6
7 8 9 10 11 12 13 14
... ...
0
−
1 2
/ +
3 4 5 6
(b) ∗ + ∗ 6
7 8 9 10 11 12
+ 3 − 2 3 −
15 16 19 20 25 26
3 1 9 5 7 4
Figure 8.10: Binary tree level numbering: (a) general scheme; (b) an example.
www.it-ebooks.info
332 Chapter 8. Trees
The level numbering function f suggests a representation of a binary tree T by
means of an array-based structure A, with the element at position p of T stored at
index f (p) of the array. We show an example of an array-based representation of a
binary tree in Figure 8.11.
0
/
1 2
∗ +
3 4 5 6
+ 4 − 2
7 8 11 12
3 1 9 5
/ ∗ + + 4 − 2 3 1 9 5
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
www.it-ebooks.info
8.3. Implementing Trees 333
parent
New York
element
children
(a) (b)
Figure 8.12: The linked structure for a general tree: (a) the structure of a node; (b) a
larger portion of the data structure associated with a node and its children.
Table 8.2: Running times of the accessor methods of an n-node general tree im-
plemented with a linked structure. We let c p denote the number of children of a
position p, and d p its depth. The space usage is O(n).
www.it-ebooks.info
334 Chapter 8. Trees
Algorithm preorder(p):
perform the “visit” action for position p { this happens before any recursion }
for each child c in children(p) do
preorder(c) { recursively traverse the subtree rooted at c }
Code Fragment 8.12: Algorithm preorder for performing the preorder traversal of a
subtree rooted at position p of a tree.
Figure 8.13 portrays the order in which positions of a sample tree are visited
during an application of the preorder traversal algorithm.
Paper
Figure 8.13: Preorder traversal of an ordered tree, where the children of each posi-
tion are ordered from left to right.
www.it-ebooks.info
8.4. Tree Traversal Algorithms 335
Postorder Traversal
Algorithm postorder(p):
for each child c in children(p) do
postorder(c) { recursively traverse the subtree rooted at c }
perform the “visit” action for position p { this happens after any recursion }
Code Fragment 8.13: Algorithm postorder for performing the postorder traversal of
a subtree rooted at position p of a tree.
Paper
Running-Time Analysis
Both preorder and postorder traversal algorithms are efficient ways to access all the
positions of a tree. The analysis of either of these traversal algorithms is similar
to that of algorithm height, given in Code Fragment 8.5 of Section 8.1.3. At each
position p, the nonrecursive part of the traversal algorithm requires time O(c p + 1),
where c p is the number of children of p, under the assumption that the “visit” itself
takes O(1) time. By Proposition 8.4, the overall running time for the traversal of
tree T is O(n), where n is the number of positions in the tree. This running time is
asymptotically optimal since the traversal must visit all n positions of the tree.
www.it-ebooks.info
336 Chapter 8. Trees
2 3 X
4
X
X
O O X O X O X X X O X X X X X
X X O O O O
O O O
5 6 7 8 9 10 11 12 13 14 15 16
Figure 8.15: Partial game tree for Tic-Tac-Toe when ignoring symmetries; annota-
tions denote the order in which positions are visited in a breadth-first tree traversal.
Algorithm breadthfirst( ):
Initialize queue Q to contain root( )
while Q not empty do
p = Q.dequeue( ) { p is the oldest entry in the queue }
perform the “visit” action for position p
for each child c in children(p) do
Q.enqueue(c) { add p’s children to the end of the queue for later visits }
Code Fragment 8.14: Algorithm for performing a breadth-first traversal of a tree.
www.it-ebooks.info
8.4. Tree Traversal Algorithms 337
Algorithm inorder(p):
if p has a left child lc then
inorder(lc) { recursively traverse the left subtree of p }
perform the “visit” action for position p
if p has a right child rc then
inorder(rc) { recursively traverse the right subtree of p }
/ +
× + × 6
+ 3 − 2 3 −
3 1 9 5 7 4
The inorder traversal algorithm has several important applications. When using
a binary tree to represent an arithmetic expression, as in Figure 8.16, the inorder
traversal visits positions in a consistent order with the standard representation of
the expression, as in 3 + 1 × 3/9 − 5 + 2 . . . (albeit without parentheses).
www.it-ebooks.info
338 Chapter 8. Trees
Binary Search Trees
An important application of the inorder traversal algorithm arises when we store an
ordered sequence of elements in a binary tree, defining a structure we call a binary
search tree. Let S be a set whose unique elements have an order relation. For
example, S could be a set of integers. A binary search tree for S is a proper binary
tree T such that, for each internal position p of T :
• Position p stores an element of S, denoted as e(p).
• Elements stored in the left subtree of p (if any) are less than e(p).
• Elements stored in the right subtree of p (if any) are greater than e(p).
An example of a binary search tree is shown in Figure 8.17. The above properties
assure that an inorder traversal of a binary search tree T visits the elements in
nondecreasing order.
58
31 90
25 42 62
12 36 75
Figure 8.17: A binary search tree storing integers. The solid path is traversed when
searching (successfully) for 42. The dashed path is traversed when searching (un-
successfully) for 70.
We can use a binary search tree T for set S to find whether a given search value
v is in S, by traversing a path down the tree T , starting at the root. At each internal
position p encountered, we compare our search value v with the element e(p) stored
at p. If v < e(p), then the search continues in the left subtree of p. If v = e(p), then
the search terminates successfully. If v > e(p), then the search continues in the
right subtree of p. Finally, if we reach a leaf, the search terminates unsuccessfully.
In other words, a binary search tree can be viewed as a binary decision tree (recall
Example 8.5), where the question asked at each internal node is whether the ele-
ment at that node is less than, equal to, or larger than the element being searched
for. We illustrate several examples of the search operation in Figure 8.17.
Note that the running time of searching in a binary search tree T is proportional
to the height of T . Recall from Proposition 8.7 that the height of a binary tree with
n nodes can be as small as log(n + 1) − 1 or as large as n − 1. Thus, binary search
trees are most efficient when they have small height. Chapter 11 is devoted to the
study of search trees.
www.it-ebooks.info
8.4. Tree Traversal Algorithms 339
www.it-ebooks.info
340 Chapter 8. Trees
Preorder Traversals
1 /∗∗ Adds positions of the subtree rooted at Position p to the given snapshot. ∗/
2 private void preorderSubtree(Position<E> p, List<Position<E>> snapshot) {
3 snapshot.add(p); // for preorder, we add position p before exploring subtrees
4 for (Position<E> c : children(p))
5 preorderSubtree(c, snapshot);
6 }
Code Fragment 8.18: A recursive subroutine for performing a preorder traversal of
the subtree rooted at position p of a tree. This code should be included within the
body of the AbstractTree class.
www.it-ebooks.info
8.4. Tree Traversal Algorithms 341
Postorder Traversal
We implement a postorder traversal using a similar design as we used for a pre-
order traversal. The only difference is that a “visited” position is not added to a
postorder snapshot until after all of its subtrees have been traversed. Both the re-
cursive utility and the top-level public method are given in Code Fragment 8.20.
1 /∗∗ Adds positions of the subtree rooted at Position p to the given snapshot. ∗/
2 private void postorderSubtree(Position<E> p, List<Position<E>> snapshot) {
3 for (Position<E> c : children(p))
4 postorderSubtree(c, snapshot);
5 snapshot.add(p); // for postorder, we add position p after exploring subtrees
6 }
7 /∗∗ Returns an iterable collection of positions of the tree, reported in postorder. ∗/
8 public Iterable<Position<E>> postorder( ) {
9 List<Position<E>> snapshot = new ArrayList<>( );
10 if (!isEmpty( ))
11 postorderSubtree(root( ), snapshot); // fill the snapshot recursively
12 return snapshot;
13 }
Code Fragment 8.20: Support for performing a postorder traversal of a tree. This
code should be included within the body of the AbstractTree class.
Breadth-First Traversal
On the following page, we will provide an implementation of the breadth-first
traversal algorithm in the context of our AbstractTree class (Code Fragment 8.21).
Recall that the breadth-first traversal algorithm is not recursive; it relies on a queue
of positions to manage the traversal process. We will use the LinkedQueue class
from Section 6.2.3, although any implementation of the queue ADT would suffice.
www.it-ebooks.info
342 Chapter 8. Trees
1 /∗∗ Adds positions of the subtree rooted at Position p to the given snapshot. ∗/
2 private void inorderSubtree(Position<E> p, List<Position<E>> snapshot) {
3 if (left(p) != null)
4 inorderSubtree(left(p), snapshot);
5 snapshot.add(p);
6 if (right(p) != null)
7 inorderSubtree(right(p), snapshot);
8 }
9 /∗∗ Returns an iterable collection of positions of the tree, reported in inorder. ∗/
10 public Iterable<Position<E>> inorder( ) {
11 List<Position<E>> snapshot = new ArrayList<>( );
12 if (!isEmpty( ))
13 inorderSubtree(root( ), snapshot); // fill the snapshot recursively
14 return snapshot;
15 }
16 /∗∗ Overrides positions to make inorder the default order for binary trees. ∗/
17 public Iterable<Position<E>> positions( ) {
18 return inorder( );
19 }
Code Fragment 8.22: Support for performing an inorder traversal of a binary tree,
and for making that order the default traversal for binary trees. This code should be
included within the body of the AbstractBinaryTree class.
www.it-ebooks.info
8.4. Tree Traversal Algorithms 343
Table of Contents
Paper Paper
Title Title
Abstract Abstract
§1 §1
§1.1 §1.1
§1.2 §1.2
§2 §2
§2.1 §2.1
... ...
(a) (b)
Figure 8.18: Table of contents for a document represented by the tree in Figure 8.13:
(a) without indentation; (b) with indentation based on depth within the tree.
The unindented version of the table of contents can be produced with the fol-
lowing code, given a tree T supporting the preorder( ) method:
for (Position<E> p : T.preorder( ))
System.out.println(p.getElement( ));
To produce the presentation of Figure 8.18(b), we indent each element with
a number of spaces equal to twice the element’s depth in the tree (hence, the
root element was unindented). If we assume that method, spaces(n), produces a
string of n spaces, we could replace the body of the above loop with the statement
System.out.println(spaces(2∗T.depth(p)) + p.getElement( )). Unfortunately, al-
though the work to produce the preorder traversal runs in O(n) time, based on the
analysis of Section 8.4.1, the calls to depth incur a hidden cost. Making a call to
depth from every position of the tree results in O(n2 ) worst-case time, as noted
when analyzing the algorithm heightBad in Section 8.1.3.
www.it-ebooks.info
344 Chapter 8. Trees
A preferred approach to producing an indented table of contents is to redesign
a top-down recursion that includes the current depth as an additional parameter.
Such an implementation is provided in Code Fragment 8.23. This implementation
runs in worst-case O(n) time (except, technically, the time it takes to print strings
of increasing lengths).
1 /∗∗ Prints preorder representation of subtree of T rooted at p having depth d. ∗/
2 public static <E> void printPreorderIndent(Tree<E> T, Position<E> p, int d) {
3 System.out.println(spaces(2∗d) + p.getElement( )); // indent based on d
4 for (Position<E> c : T.children(p))
5 printPreorderIndent(T, c, d+1); // child depth is d+1
6 }
Code Fragment 8.23: Efficient recursion for printing indented version of a pre-
order traversal. To print an entire tree T, the recursion should be started with form
printPreorderIndent(T, T.root( ), 0).
In the example of Figure 8.18, we were fortunate in that the numbering was
embedded within the elements of the tree. More generally, we might be interested
in using a preorder traversal to display the structure of a tree, with indentation and
also explicit numbering that was not present in the tree. For example, we might
display the tree from Figure 8.2 beginning as:
Electronics R’Us
1 R&D
2 Sales
2.1 Domestic
2.2 International
2.2.1 Canada
2.2.2 S. America
This is more challenging, because the numbers used as labels are implicit in
the structure of the tree. A label depends on the path from the root to the current
position. To accomplish our goal, we add an additional parameter to the recursive
signature. We send a list of integers representing the labels leading to a particular
position. For example, when visiting the node Domestic above, we will send the
list of values {2, 1} that comprise its label.
At the implementation level, we wish to avoid the inefficiency of duplicating
such lists when sending a new parameter from one level of the recursion to the next.
A standard solution is to pass the same list instance throughout the recursion. At
one level of the recursion, a new entry is temporarily added to the end of the list
before making further recursive calls. In order to “leave no trace,” the extraneous
entry must later be removed from the list by the same recursive call that added it.
An implementation based on this approach is given in Code Fragment 8.24.
www.it-ebooks.info
8.4. Tree Traversal Algorithms 345
1 /∗∗ Prints labeled representation of subtree of T rooted at p having depth d. ∗/
2 public static <E>
3 void printPreorderLabeled(Tree<E> T, Position<E> p, ArrayList<Integer> path) {
4 int d = path.size( ); // depth equals the length of the path
5 System.out.print(spaces(2∗d)); // print indentation, then label
6 for (int j=0; j < d; j++) System.out.print(path.get(j) + (j == d−1 ? " " : "."));
7 System.out.println(p.getElement( ));
8 path.add(1); // add path entry for first child
9 for (Position<E> c : T.children(p)) {
10 printPreorderLabeled(T, c, path);
11 path.set(d, 1 + path.get(d)); // increment last entry of path
12 }
13 path.remove(d); // restore path to its incoming state
14 }
Code Fragment 8.24: Efficient recursion for printing an indented and labeled pre-
sentation of a preorder traversal.
In Example 8.1, we considered the use of a tree as a model for a file-system struc-
ture, with internal positions representing directories and leaves representing files.
In fact, when introducing the use of recursion back in Chapter 5, we specifically
examined the topic of file systems (see Section 5.1.4). Although we did not explic-
itly model it as a tree at that time, we gave an implementation of an algorithm for
computing the disk usage (Code Fragment 5.5).
The recursive computation of disk space is emblematic of a postorder traversal,
as we cannot effectively compute the total space used by a directory until after we
know the space that is used by its children directories. Unfortunately, the formal
implementation of postorder, as given in Code Fragment 8.20, does not suffice for
this purpose. We would like to have a mechanism for children to return information
to the parent as part of the traversal process. A custom solution to the disk space
problem, with each level of recursion providing a return value to the (parent) caller,
is provided in Code Fragment 8.25.
www.it-ebooks.info
346 Chapter 8. Trees
Parenthetic Representations of a Tree
It is not possible to reconstruct a general tree, given only the preorder sequence of
elements, as in Figure 8.18a. Some additional context is necessary for the structure
of the tree to be well defined. The use of indentation or numbered labels provides
such context, with a very human-friendly presentation. However, there are more
concise string representations of trees that are computer-friendly.
In this section, we explore one such representation. The parenthetic string
representation P(T ) of tree T is recursively defined. If T consists of a single
position p, then P(T ) = p.getElement( ). Otherwise, it is defined recursively as,
P(T ) = p.getElement( ) + "(" + P(T1 ) + ", " + · · · + ", " + P(Tk ) + ")"
where p is the root of T and T1 , T2 , . . . , Tk are the subtrees rooted at the children
of p, which are given in order if T is an ordered tree. We are using “+” here to
denote string concatenation. As an example, the parenthetic representation of the
tree of Figure 8.2 would appear as follows (line breaks are cosmetic):
www.it-ebooks.info
8.4. Tree Traversal Algorithms 347
Using Inorder Traversal for Tree Drawing
An inorder traversal can be applied to the problem of computing a graphical layout
of a binary tree, as shown in Figure 8.19. We assume the convention, common
to computer graphics, that x-coordinates increase left to right and y-coordinates
increase top to bottom, so that the origin is in the upper left corner of the drawing.
0 1 2 3 4 5 6 7 8 9 10 11 12
0
www.it-ebooks.info
348 Chapter 8. Trees
/ +
× + × 6
+ 3 − 2 3 −
3 1 9 5 7 4
The complexity of the walk is O(n), for a tree with n nodes, because it pro-
gresses exactly two times along each of the n − 1 edges of the tree—once going
downward along the edge, and later going upward along the edge. To unify the
concept of preorder and postorder traversals, we can view there being two notable
“visits” to each position p:
• A “pre visit” occurs when first reaching the position, that is, when the walk
passes immediately left of the node in our visualization.
• A “post visit” occurs when the walk later proceeds upward from that position,
that is, when the walk passes to the right of the node in our visualization.
The process of an Euler tour can be naturally viewed as recursive. In between
the “pre visit” and “post visit” of a given position will be a recursive tour of each
of its subtrees. Looking at Figure 8.20 as an example, there is a contiguous portion
of the entire tour that is itself an Euler tour of the subtree of the node with element
“/ ”. That tour contains two contiguous subtours, one traversing that position’s left
subtree and another traversing the right subtree.
In the special case of a binary tree, we can designate the time when the walk
passes immediately below a node as an “in visit” event. This will be just after the
tour of its left subtree (if any), but before the tour of its right subtree (if any).
www.it-ebooks.info
8.4. Tree Traversal Algorithms 349
The pseudocode for an Euler tour traversal of a subtree rooted at a position p is
shown in Code Fragment 8.28.
Algorithm eulerTour(T , p):
perform the “pre visit” action for position p
for each child c in T .children(p) do
eulerTour(T , c) { recursively tour the subtree rooted at c }
perform the “post visit” action for position p
Code Fragment 8.28: Algorithm eulerTour for performing an Euler tour traversal of
a subtree rooted at position p of a tree.
The Euler tour traversal extends the preorder and postorder traversals, but it can
also perform other kinds of traversals. For example, suppose we wish to compute
the number of descendants of each position p in an n-node binary tree. We start an
Euler tour by initializing a counter to 0, and then increment the counter during the
“pre visit” for each position. To determine the number of descendants of a posi-
tion p, we compute the difference between the values of the counter from when the
pre-visit occurs and when the post-visit occurs, and add 1 (for p). This simple rule
gives us the number of descendants of p, because each node in the subtree rooted
at p is counted between p’s visit on the left and p’s visit on the right. Therefore, we
have an O(n)-time method for computing the number of descendants of each node.
For the case of a binary tree, we can customize the algorithm to include an
explicit “in visit” action, as shown in Code Fragment 8.29.
Algorithm eulerTourBinary(T , p):
perform the “pre visit” action for position p
if p has a left child lc then
eulerTourBinary(T , lc) { recursively tour the left subtree of p }
perform the “in visit” action for position p
if p has a right child rc then
eulerTourBinary(T , rc) { recursively tour the right subtree of p }
perform the “post visit” action for position p
Code Fragment 8.29: Algorithm eulerTourBinary for performing an Euler tour
traversal of a subtree rooted at position p of a binary tree.
For example, a binary Euler tour can produce a traditional parenthesized arith-
metic expression, such as "((((3+1)x3)/((9-5)+2))-((3x(7-4))+6))" for
the tree in Figure 8.20, as follows:
• “Pre visit” action: if the position is internal, print “(”.
• “In visit” action: print the value or operator stored at the position.
• “Post visit” action: if the position is internal, print “)”.
www.it-ebooks.info