0% found this document useful (0 votes)
413 views

Extracted Pages From Data Structures and Algorithms in Python (300-361)

Trees are a nonlinear data structure that store elements hierarchically. Each element has a parent element and zero or more child elements, except for the top element called the root. Trees provide a natural organization of data and allow algorithms to run faster than linear data structures like lists. Common terminology like parent, child, ancestor, and descendant describe the hierarchical relationships between elements in a tree.

Uploaded by

Star
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
413 views

Extracted Pages From Data Structures and Algorithms in Python (300-361)

Trees are a nonlinear data structure that store elements hierarchically. Each element has a parent element and zero or more child elements, except for the top element called the root. Trees provide a natural organization of data and allow algorithms to run faster than linear data structures like lists. Common terminology like parent, child, ancestor, and descendant describe the hierarchical relationships between elements in a tree.

Uploaded by

Star
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 62

300 Chapter 8.

Trees

8.1 General Trees


Productivity experts say that breakthroughs come by thinking “nonlinearly.” In
this chapter, we discuss one of the most important nonlinear data structures in
computing—trees. Tree structures are indeed a breakthrough in data organization,
for they allow us to implement a host of algorithms much faster than when using
linear data structures, such as array-based lists or linked lists. Trees also provide a
natural organization for data, and consequently have become ubiquitous structures
in file systems, graphical user interfaces, databases, Web sites, and other computer
systems.
It is not always clear what productivity experts mean by “nonlinear” thinking,
but when we say that trees are “nonlinear,” we are referring to an organizational
relationship that is richer than the simple “before” and “after” relationships be-
tween objects in sequences. The relationships in a tree are hierarchical, with some
objects being “above” and some “below” others. Actually, the main terminology
for tree data structures comes from family trees, with the terms “parent,” “child,”
“ancestor,” and “descendant” being the most common words used to describe rela-
tionships. We show an example of a family tree in Figure 8.1.
Abraham
Isaac

Zimran
Ishmael

Medan

Shuah
Midian

Ishbak
Jokshan
Tema

Jacob (Israel)

Abida
Epher
Nebaioth

Dumah
Massa
Hadad

Naphish

Ephah

Eldaah
Dedan
Mibsam
Mishma

Esau

Sheba
Kedar
Adbeel

Jetur
Kedemah

Hanoch
Simeon

Benjamin
Dinah
Jalam

Asher
Reuel

Naphtali

Zebulun
Jeush

Judah
Dan
Gad

Joseph
Eliphaz

Issachar
Levi
Korah
Reuben

Figure 8.1: A family tree showing some descendants of Abraham, as recorded in


Genesis, chapters 25–36.

www.it-ebooks.info
8.1. General Trees 301

8.1.1 Tree Definitions and Properties


A tree is an abstract data type that stores elements hierarchically. With the excep-
tion of the top element, each element in a tree has a parent element and zero or
more children elements. A tree is usually visualized by placing elements inside
ovals or rectangles, and by drawing the connections between parents and children
with straight lines. (See Figure 8.2.) We typically call the top element the root
of the tree, but it is drawn as the highest element, with the other elements being
connected below (just the opposite of a botanical tree).

Electronics R’Us

R&D Sales Purchasing Manufacturing

Domestic International TV CD Tuner

Canada S. America Overseas

Africa Europe Asia Australia

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.

Formal Tree Definition


Formally, we define a tree T as a set of nodes storing elements such that the nodes
have a parent-child relationship that satisfies the following properties:
• If T is nonempty, it has a special node, called the root of T , that has no parent.
• Each node v of T different from the root has a unique parent node w; every
node with parent w is a child of w.
Note that according to our definition, a tree can be empty, meaning that it does not
have any nodes. This convention also allows us to define a tree recursively such
that a tree T is either empty or consists of a node r, called the root of T , and a
(possibly empty) set of subtrees whose roots are the children of r.

www.it-ebooks.info
302 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 4.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/

hw1 hw2 hw3 pr1 pr2 pr3


papers/ demos/

buylow sellhigh market

Figure 8.3: Tree representing a portion of a file system.

A node u is an ancestor of a node v if u = v or u is an ancestor of the parent


of v. Conversely, we say that a node v is a descendant of a node u if u is an ancestor
of v. For example, in Figure 8.3, cs252/ is an ancestor of papers/, and pr3 is a
descendant of cs016/. The subtree of T rooted at a node v is the tree consisting of
all the descendants of v in T (including v itself). In Figure 8.3, the subtree rooted at
cs016/ consists of the nodes cs016/, grades, homeworks/, programs/, hw1, hw2,
hw3, pr1, pr2, and pr3.

Edges and Paths in Trees


An edge of tree T is a pair of nodes (u, v) such that u is the parent of v, or vice
versa. A path of T is a sequence of nodes such that any two consecutive nodes in
the sequence form an edge. For example, the tree in Figure 8.3 contains the path
(cs252/, projects/, demos/, market).

www.it-ebooks.info
8.1. General Trees 303
Example 8.2: The inheritance relation between classes in a Python program forms
a tree when single inheritance is used. For example, in Section 2.4 we provided a
summary of the hierarchy for Python’s exception types, as portrayed in Figure 8.4
(originally Figure 2.5). The BaseException class is the root of that hierarchy, while
all user-defined exception classes should conventionally be declared as descendants
of the more specific Exception class. (See, for example, the Empty class we intro-
duced in Code Fragment 6.1 of Chapter 6.)

BaseException

SystemExit Exception KeyboardInterrupt

ValueError LookupError ArithmeticError

IndexError KeyError ZeroDivisionError

Figure 8.4: A portion of Python’s hierarchy of exception types.

In Python, all classes are organized into a single hierarchy, as there exists a
built-in class named object as the ultimate base class. It is a direct or indirect base
class of all other types in Python (even if not declared as such when defining a new
class). Therefore, the hierarchy pictured in Figure 8.4 is only a portion of Python’s
complete class hierarchy.
As a preview of the remainder of this chapter, Figure 8.5 portrays our own
hierarchy of classes for representing various forms of a tree.

Tree

BinaryTree LinkedTree

ArrayBinaryTree LinkedBinaryTree

Figure 8.5: Our own inheritance hierarchy for modeling various abstractions and
implementations of tree data structures. In the remainder of this chapter, we provide
implementations of Tree, BinaryTree, and LinkedBinaryTree classes, and high-
level sketches for how LinkedTree and ArrayBinaryTree might be designed.

www.it-ebooks.info
304 Chapter 8. Trees
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.3: 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.6.) 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

Preface Part A Part B References

¶ ... ¶ Ch. 1 ... Ch. 5 Ch. 6 ... Ch. 9 ¶ ... ¶

§ 1.1 ... § 1.4 § 5.1 ... § 5.7 § 6.1 ... § 6.5 § 9.1 ... § 9.6

¶ ... ¶ ... ¶ ... ¶

Figure 8.6: An ordered tree associated with a book.

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 8.4, 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
8.1. General Trees 305

8.1.2 The Tree Abstract Data Type


As we did with positional lists in Section 7.4, we define a tree ADT using the
concept of a position as an abstraction for a node of a tree. An element is stored
at each position, and positions satisfy parent-child relationships that define the tree
structure. A position object for a tree supports the method:

p.element( ): Return the element stored at position p.

The tree ADT then supports the following accessor methods, allowing a user to
navigate the various positions of a tree:

T.root( ): Return the position of the root of tree T,


or None if T is empty.

T.is root(p): Return True if position p is the root of Tree T.

T.parent(p): Return the position of the parent of position p,


or None if p is the root of T.

T.num children(p): Return the number of children of position p.

T.children(p): Generate an iteration of the children of position p.

T.is leaf(p): Return True if position p does not have any children.

len(T): Return the number of positions (and hence elements) that


are contained in tree T.

T.is empty( ): Return True if tree T does not contain any positions.

T.positions( ): Generate an iteration of all positions of tree T.

iter(T): Generate an iteration of all elements stored within tree T.

Any of the above methods that accepts a position as an argument should generate a
ValueError if that position is invalid for T.
If a tree T is ordered, then T.children(p) reports the children of p in the natural
order. If p is a leaf, then T.children(p) generates an empty iteration. In similar
regard, if tree T is empty, then both T.positions( ) and iter(T) generate empty iter-
ations. We will discuss general means for iterating through all positions of a tree in
Sections 8.4.
We do not define any methods for creating or modifying trees at this point.
We prefer to describe different tree update methods in conjunction with specific
implementations of the tree interface, and specific applications of trees.

www.it-ebooks.info
306 Chapter 8. Trees

A Tree Abstract Base Class in Python


In discussing the object-oriented design principle of abstraction in Section 2.1.2, we
noted that a public interface for an abstract data type is often managed in Python via
duck typing. For example, we defined the notion of the public interface for a queue
ADT in Section 6.2, and have since presented several classes that implement the
queue interface (e.g., ArrayQueue in Section 6.2.2, LinkedQueue in Section 7.1.2,
CircularQueue in Section 7.2.2). However, we never gave any formal definition of
the queue ADT in Python; all of the concrete implementations were self-contained
classes that just happen to adhere to the same public interface. A more formal
mechanism to designate the relationships between different implementations of the
same abstraction is through the definition of one class that serves as an abstract
base class, via inheritance, for one or more concrete classes. (See Section 2.4.3.)
We choose to define a Tree class, in Code Fragment 8.1, that serves as an ab-
stract base class corresponding to the tree ADT. Our reason for doing so is that there
is quite a bit of useful code that we can provide, even at this level of abstraction, al-
lowing greater code reuse in the concrete tree implementations we later define. The
Tree class provides a definition of a nested Position class (which is also abstract),
and declarations of many of the accessor methods included in the tree ADT.
However, our Tree class does not define any internal representation for stor-
ing a tree, and five of the methods given in that code fragment remain abstract
(root, parent, num children, children, and len ); each of these methods raises a
NotImplementedError. (A more formal approach for defining abstract base classes
and abstract methods, using Python’s abc module, is described in Section 2.4.3.)
The subclasses are responsible for overriding abstract methods, such as children, to
provide a working implementation for each behavior, based on their chosen internal
representation.
Although the Tree class is an abstract base class, it includes several concrete
methods with implementations that rely on calls to the abstract methods of the class.
In defining the tree ADT in the previous section, we declare ten accessor methods.
Five of those are the ones we left as abstract, in Code Fragment 8.1. The other five
can be implemented based on the former. Code Fragment 8.2 provides concrete
implementations for methods is root, is leaf, and is empty. In Section 8.4, we will
explore general algorithms for traversing a tree that can be used to provide concrete
implementations of the positions and iter methods within the Tree class. The
beauty of this design is that the concrete methods defined within the Tree abstract
base class will be inherited by all subclasses. This promotes greater code reuse, as
there will be no need for those subclasses to reimplement such behaviors.
We note that, with the Tree class being abstract, there is no reason to create a
direct instance of it, nor would such an instance be useful. The class exists to serve
as a base for inheritance, and users will create instances of concrete subclasses.

www.it-ebooks.info
8.1. General Trees 307
1 class Tree:
2 ”””Abstract base class representing a tree structure.”””
3
4 #------------------------------- nested Position class -------------------------------
5 class Position:
6 ”””An abstraction representing the location of a single element.”””
7
8 def element(self):
9 ”””Return the element stored at this Position.”””
10 raise NotImplementedError( must be implemented by subclass )
11
12 def eq (self, other):
13 ”””Return True if other Position represents the same location.”””
14 raise NotImplementedError( must be implemented by subclass )
15
16 def ne (self, other):
17 ”””Return True if other does not represent the same location.”””
18 return not (self == other) # opposite of eq
19
20 # ---------- abstract methods that concrete subclass must support ----------
21 def root(self):
22 ”””Return Position representing the tree s root (or None if empty).”””
23 raise NotImplementedError( must be implemented by subclass )
24
25 def parent(self, p):
26 ”””Return Position representing p s parent (or None if p is root).”””
27 raise NotImplementedError( must be implemented by subclass )
28
29 def num children(self, p):
30 ”””Return the number of children that Position p has.”””
31 raise NotImplementedError( must be implemented by subclass )
32
33 def children(self, p):
34 ”””Generate an iteration of Positions representing p s children.”””
35 raise NotImplementedError( must be implemented by subclass )
36
37 def len (self):
38 ”””Return the total number of elements in the tree.”””
39 raise NotImplementedError( must be implemented by subclass )
Code Fragment 8.1: A portion of our Tree abstract base class (continued in Code
Fragment 8.2).

www.it-ebooks.info
308 Chapter 8. Trees
40 # ---------- concrete methods implemented in this class ----------
41 def is root(self, p):
42 ”””Return True if Position p represents the root of the tree.”””
43 return self.root( ) == p
44
45 def is leaf(self, p):
46 ”””Return True if Position p does not have any children.”””
47 return self.num children(p) == 0
48
49 def is empty(self):
50 ”””Return True if the tree is empty.”””
51 return len(self) == 0
Code Fragment 8.2: Some concrete methods of our Tree abstract base class.

8.1.3 Computing Depth and Height


Let p be the position of a node of a tree T . The depth of p is the number of
ancestors of p, excluding p itself. For example, in the tree of Figure 8.2, the node
storing International has depth 2. Note that this definition implies that the depth of
the root of T is 0. The depth of p can also be recursively defined as follows:
• If p is the root, then the depth of p is 0.
• Otherwise, the depth of p is one plus the depth of the parent of p.
Based on this definition, we present a simple, recursive algorithm, depth, in Code
Fragment 8.3, for computing the depth of a position p in Tree T . This method calls
itself recursively on the parent of p, and adds 1 to the value returned.
52 def depth(self, p):
53 ”””Return the number of levels separating Position p from the root.”””
54 if self.is root(p):
55 return 0
56 else:
57 return 1 + self.depth(self.parent(p))
Code Fragment 8.3: Method depth of the Tree class.

The running time of T.depth(p) for position p is O(d p + 1), where d p denotes
the depth of p in the tree T , because the algorithm performs a constant-time recur-
sive step for each ancestor of p. Thus, algorithm T.depth(p) runs in O(n) worst-
case time, where n is the total number of positions of T , because a position of T
may have depth n − 1 if all nodes form a single branch. Although such a running
time is a function of the input size, it is more informative to characterize the running
time in terms of the parameter d p , as this parameter may be much smaller than n.

www.it-ebooks.info
8.1. General Trees 309
Height
The height of a position p in a tree T is also defined recursively:
• 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.
The height of a nonempty tree T is the height of the root of T . For example, the
tree of Figure 8.2 has height 4. In addition, height can also be viewed as follows.
Proposition 8.4: The height of a nonempty tree T is equal to the maximum of
the depths of its leaf positions.
We leave the justification of this fact to an exercise (R-8.3). We present an
algorithm, height1, implemented in Code Fragment 8.4 as a nonpublic method
height1 of the Tree class. It computes the height of a nonempty tree T based on
Proposition 8.4 and the algorithm depth from Code Fragment 8.3.
58 def height1(self): # works, but O(nˆ2) worst-case time
59 ”””Return the height of the tree.”””
60 return max(self.depth(p) for p in self.positions( ) if self.is leaf(p))
Code Fragment 8.4: Method height1 of the Tree class. Note that this method calls
the depth method.

Unfortunately, algorithm height1 is not very efficient. We have not yet defined
the positions( ) method; we will see that it can be implemented to run in O(n) time,
where n is the number of positions of T . Because height1 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.33.) Thus, algorithm height1 runs in O(n2 ) worst-case time.
We can compute the height of a tree more efficiently, in O(n) worst-case time,
by relying instead on the original recursive definition. To do this, we will param-
eterize a function based on a position within the tree, and calculate the height of
the subtree rooted at that position. Algorithm height2, shown as nonpublic method
height2 in Code Fragment 8.5, computes the height of tree T in this way.
61 def height2(self, p): # time is linear in size of subtree
62 ”””Return the height of the subtree rooted at Position p.”””
63 if self.is leaf(p):
64 return 0
65 else:
66 return 1 + max(self. height2(c) for c in self.children(p))
Code Fragment 8.5: Method height2 for computing the height of a subtree rooted
at a position p of a Tree.

www.it-ebooks.info
310 Chapter 8. Trees
It is important to understand why algorithm height2 is more efficient than
height1. The algorithm is recursive, and it progresses in a top-down fashion. If
the method is initially called on the root of T , it will eventually be called once for
each position of T . This is because the root eventually invokes the recursion on
each of its children, which in turn invokes the recursion on each of their children,
and so on.
We can determine the running time of the height2 algorithm by summing, over
all the positions, the amount of time spent on the nonrecursive part of each call.
(Review Section 4.2 for analyses of recursive processes.) In our implementation,
there is a constant amount of work per position, plus the overhead of computing the
maximum over the iteration of children. Although we do not yet have a concrete
implementation of children(p), we assume that such an iteration is generated in
O(c p + 1) time, where c p denotes the number of children of p. Algorithm height2
spends O(c p + 1) time at each position p to compute the maximum, and its overall
running time is O(∑ p (c p + 1)) = O(n + ∑ p c p ). In order to complete the analysis,
we make use of the following property.
Proposition 8.5: Let T be a tree with n positions, and let c p denote the number of
children of a position p of T . Then, summing over the positions of T , ∑ p c p = n− 1.

Justification: Each position of T , with the exception of the root, is a child of


another position, and thus contributes one unit to the above sum.
By Proposition 8.5, the running time of algorithm height2, when called on the
root of T , is O(n), where n is the number of positions of T .
Revisiting the public interface for our Tree class, the ability to compute heights
of subtrees is beneficial, but a user might expect to be able to compute the height
of the entire tree without explicitly designating the tree root. We can wrap the non-
public height2 in our implementation with a public height method that provides
a default interpretation when invoked on tree T with syntax T.height( ). Such an
implementation is given in Code Fragment 8.6.

67 def height(self, p=None):


68 ”””Return the height of the subtree rooted at Position p.
69
70 If p is None, return the height of the entire tree.
71 ”””
72 if p is None:
73 p = self.root( )
74 return self. height2(p) # start height2 recursion
Code Fragment 8.6: Public method Tree.height that computes the height of the
entire tree by default, or a subtree rooted at given position, if specified.

www.it-ebooks.info
8.2. Binary Trees 311

8.2 Binary Trees


A binary tree is an ordered tree with the following properties:
1. Every node has at most two children.
2. Each child node is labeled as being either a left child or a right child.
3. A left child precedes a right child in the order of children of a node.
The subtree rooted at a left or right child of an internal node v is called a left subtree
or right subtree, respectively, of v. A binary tree is proper if each node has either
zero or two children. Some people also refer to such trees as being full binary
trees. Thus, in a proper binary tree, every internal node has exactly two children.
A binary tree that is not proper is improper.
Example 8.6: An important class of binary trees arises in contexts where we wish
to represent a number of different outcomes that can result from answering a series
of yes-or-no questions. Each internal node is associated with a question. Starting at
the root, we go to the left or right child of the current node, depending on whether
the answer to the question is “Yes” or “No.” With each decision, we follow an
edge from a parent to a child, eventually tracing a path in the tree from the root
to a leaf. Such binary trees are known as decision trees, because a leaf position p
in such a tree represents a decision of what to do if the questions associated with
p’s ancestors are answered in a way that leads to p. A decision tree is a proper
binary tree. Figure 8.7 illustrates a decision tree that provides recommendations to
a prospective investor.

Are you nervous?

Yes No

Will you need to access most of the


Savings account. money within the next 5 years?

Yes No

Are you willing to accept risks in


Money market fund.
exchange for higher expected returns?

Yes No

Diversified portfolio with stocks,


Stock portfolio. bonds, and short-term instruments.

Figure 8.7: A decision tree providing investment advice.

www.it-ebooks.info
312 Chapter 8. Trees
Example 8.7: 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 /. (See Figure 8.8.) 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.
An 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.8: A binary tree representing an arithmetic expression. This tree represents
the expression ((((3 + 1) × 3)/((9 − 5) + 2)) − ((3 × (7 − 4)) + 6)). The value
associated with the internal node labeled “/” is 2.

A Recursive Binary Tree Definition

Incidentally, we can also define a binary tree in a recursive way such that a binary
tree is either empty or consists of:

• A node r, called the root of T , that stores an element


• A binary tree (possibly empty), called the left subtree of T
• A binary tree (possibly empty), called the right subtree of T

www.it-ebooks.info
8.2. Binary Trees 313

8.2.1 The Binary Tree Abstract Data Type


As an abstract data type, a binary tree is a specialization of a tree that supports three
additional accessor methods:

T.left(p): Return the position that represents the left child of p,


or None if p has no left child.

T.right(p): Return the position that represents the right child of p,


or None if p has no right child.

T.sibling(p): Return the position that represents the sibling of p,


or None if p has no sibling.
Just as in Section 8.1.2 for the tree ADT, we do not define specialized update meth-
ods for binary trees here. Instead, we will consider some possible update methods
when we describe specific implementations and applications of binary trees.

The BinaryTree Abstract Base Class in Python

Just as Tree was defined as an abstract base class in Section 8.1.2, we define a
new BinaryTree class associated with the binary tree ADT. We rely on inheritance
to define the BinaryTree class based upon the existing Tree class. However, our
BinaryTree class remains abstract, as we still do not provide complete specifica-
tions for how such a structure will be represented internally, nor implementations
for some necessary behaviors.
Our Python implementation of the BinaryTree class is given in Code Frag-
ment 8.7. By using inheritance, a binary tree supports all the functionality that was
defined for general trees (e.g., parent, is leaf, root). Our new class also inherits the
nested Position class that was originally defined within the Tree class definition.
In addition, the new class provides declarations for new abstract methods left and
right that should be supported by concrete subclasses of BinaryTree.
Our new class also provides two concrete implementations of methods. The
new sibling method is derived from the combination of left, right, and parent. Typ-
ically, we identify the sibling of a position p as the “other” child of p’s parent.
However, if p is the root, it has no parent, and thus no sibling. Also, p may be the
only child of its parent, and thus does not have a sibling.
Finally, Code Fragment 8.7 provides a concrete implementation of the children
method; this method is abstract in the Tree class. Although we have still not speci-
fied how the children of a node will be stored, we derive a generator for the ordered
children based upon the implied behavior of abstract methods left and right.

www.it-ebooks.info
314 Chapter 8. Trees

1 class BinaryTree(Tree):
2 ”””Abstract base class representing a binary tree structure.”””
3
4 # --------------------- additional abstract methods ---------------------
5 def left(self, p):
6 ”””Return a Position representing p s left child.
7
8 Return None if p does not have a left child.
9 ”””
10 raise NotImplementedError( must be implemented by subclass )
11
12 def right(self, p):
13 ”””Return a Position representing p s right child.
14
15 Return None if p does not have a right child.
16 ”””
17 raise NotImplementedError( must be implemented by subclass )
18
19 # ---------- concrete methods implemented in this class ----------
20 def sibling(self, p):
21 ”””Return a Position representing p s sibling (or None if no sibling).”””
22 parent = self.parent(p)
23 if parent is None: # p must be the root
24 return None # root has no sibling
25 else:
26 if p == self.left(parent):
27 return self.right(parent) # possibly None
28 else:
29 return self.left(parent) # possibly None
30
31 def children(self, p):
32 ”””Generate an iteration of Positions representing p s children.”””
33 if self.left(p) is not None:
34 yield self.left(p)
35 if self.right(p) is not None:
36 yield self.right(p)
Code Fragment 8.7: A BinaryTree abstract base class that extends the existing Tree
abstract base class from Code Fragments 8.1 and 8.2.

www.it-ebooks.info
8.2. Binary Trees 315

8.2.2 Properties of Binary Trees


Binary trees have several interesting properties dealing with relationships between
their heights and number of nodes. We denote the set of all nodes of a tree T at the
same depth d as level d of T . In a binary tree, level 0 has at most one node (the
root), level 1 has at most two nodes (the children of the root), level 2 has at most
four nodes, and so on. (See Figure 8.9.) In general, level d has at most 2d nodes.
Level Nodes

0 1

1 2

2 4

3 8

... ...
...

...
Figure 8.9: Maximum number of nodes in the levels of a binary tree.

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.8: 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
316 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.9: 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.10.
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 the external-node
pile has one more node than the internal-node pile.

u u

v u
z w z z

(a) (b) (c)

Figure 8.10: Operation that removes an external node and its parent node, used in
the justification of Proposition 8.9.

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.32 through C-8.34.)

www.it-ebooks.info
8.3. Implementing Trees 317

8.3 Implementing Trees


The Tree and BinaryTree classes that we have defined thus far in this chapter are
both formally abstract base classes. Although they provide a great deal of support,
neither of them can be directly instantiated. We have not yet defined key imple-
mentation details for how a tree will be represented internally, and how we can
effectively navigate between parents and children. Specifically, a concrete imple-
mentation of a tree must provide methods root, parent, num children, children,
len , and in the case of BinaryTree, the additional accessors left and right.
There are several choices for the internal representation of trees. We describe
the most common representations in this section. We begin with the case of a
binary tree, since its shape is more narrowly defined.

8.3.1 Linked Structure for Binary Trees


A natural way to realize a binary tree T is to use a linked structure, with a node
(see Figure 8.11a) that maintains references to the element stored at a position p
and to the nodes associated with the children and parent of p. If p is the root of
T , then the parent field of p is None. Likewise, if p does not have a left child
(respectively, right child), the associated field is None. The tree itself maintains an
instance variable storing a reference to the root node (if any), and a variable, called
size, that represents the overall number of nodes of T . We show such a linked
structure representation of a binary tree in Figure 8.11b.

root
5
size

∅ ∅
parent

∅ ∅ ∅ ∅

left right Baltimore Chicago New York Providence Seattle


element

(a) (b)

Figure 8.11: A linked structure for representing: (a) a single node; (b) a binary tree.

www.it-ebooks.info
318 Chapter 8. Trees
Python Implementation of a Linked Binary Tree Structure
In this section, we define a concrete LinkedBinaryTree class that implements the
binary tree ADT by subclassing the BinaryTree class. Our general approach is very
similar to what we used when developing the PositionalList in Section 7.4: We
define a simple, nonpublic Node class to represent a node, and a public Position
class that wraps a node. We provide a validate utility for robustly checking the
validity of a given position instance when unwrapping it, and a make position
utility for wrapping a node as a position to return to a caller.
Those definitions are provided in Code Fragment 8.8. As a formality, the new
Position class is declared to inherit immediately from BinaryTree.Position. Tech-
nically, the BinaryTree class definition (see Code Fragment 8.7) does not formally
declare such a nested class; it trivially inherits it from Tree.Position. A minor ben-
efit from this design is that our position class inherits the ne special method
so that syntax p != q is derived appropriately relative to eq .
Our class definition continues, in Code Fragment 8.9, with a constructor and
with concrete implementations for the methods that remain abstract in the Tree and
BinaryTree classes. The constructor creates an empty tree by initializing root to
None and size to zero. These accessor methods are implemented with careful use
of the validate and make position utilities to safeguard against boundary cases.

Operations for Updating a Linked Binary Tree


Thus far, we have provided functionality for examining an existing binary tree.
However, the constructor for our LinkedBinaryTree class results in an empty tree
and we have not provided any means for changing the structure or content of a tree.
We chose not to declare update methods as part of the Tree or BinaryTree ab-
stract base classes for several reasons. First, although the principle of encapsula-
tion suggests that the outward behaviors of a class need not depend on the internal
representation, the efficiency of the operations depends greatly upon the representa-
tion. We prefer to have each concrete implementation of a tree class offer the most
suitable options for updating a tree.
The second reason we do not provide update methods in the base class is that
we may not want such update methods to be part of a public interface. There are
many applications of trees, and some forms of update operations that are suitable
for one application may be unacceptable in another. However, if we place an update
method in a base class, any class that inherits from that base will inherit the update
method. Consider, for example, the possibility of a method T.replace(p, e) that
replaces the element stored at position p with another element e. Such a general
method may be unacceptable in the context of an arithmetic expression tree (see
Example 8.7 on page 312, and a later case study in Section 8.5), because we may
want to enforce that internal nodes store only operators as elements.

www.it-ebooks.info
8.3. Implementing Trees 319
For linked binary trees, a reasonable set of update methods to support for gen-
eral usage are the following:
T.add root(e): Create a root for an empty tree, storing e as the element,
and return the position of that root; an error occurs if the
tree is not empty.
T.add left(p, e): Create a new node storing element e, link the node as the
left child of position p, and return the resulting position;
an error occurs if p already has a left child.
T.add right(p, e): Create a new node storing element e, link the node as the
right child of position p, and return the resulting position;
an error occurs if p already has a right child.
T.replace(p, e): Replace the element stored at position p with element e,
and return the previously stored element.
T.delete(p): Remove the node at position p, replacing it with its child,
if any, and return the element that had been stored at p;
an error occurs if p has two children.
T.attach(p, T1, T2): Attach the internal structure of trees T1 and T2, respec-
tively, as the left and right subtrees of leaf position p of
T, and reset T1 and T2 to empty trees; an error condition
occurs if p is not a leaf.
We have specifically chosen this collection of operations because each can be
implemented in O(1) worst-case time with our linked representation. The most
complex of these are delete and attach, due to the case analyses involving the
various parent-child relationships and boundary conditions, yet there remains only
a constant number of operations to perform. (The implementation of both methods
could be greatly simplified if we used a tree representation with a sentinel node,
akin to our treatment of positional lists; see Exercise C-8.40).
To avoid the problem of undesirable update methods being inherited by sub-
classes of LinkedBinaryTree, we have chosen an implementation in which none
of the above methods are publicly supported. Instead, we provide nonpublic ver-
sions of each, for example, providing the underscored delete in lieu of a public
delete. Our implementations of these six update methods are provided in Code
Fragments 8.10 and 8.11.
In particular applications, subclasses of LinkedBinaryTree can invoke the non-
public methods internally, while preserving a public interface that is appropriate
for the application. A subclass may also choose to wrap one or more of the non-
public update methods with a public method to expose it to the user. We leave as
an exercise (R-8.15), the task of defining a MutableLinkedBinaryTree subclass that
provides public methods wrapping each of these six update methods.

www.it-ebooks.info
320 Chapter 8. Trees
1 class LinkedBinaryTree(BinaryTree):
2 ”””Linked representation of a binary tree structure.”””
3
4 class Node: # Lightweight, nonpublic class for storing a node.
5 slots = _element , _parent , _left , _right
6 def init (self, element, parent=None, left=None, right=None):
7 self. element = element
8 self. parent = parent
9 self. left = left
10 self. right = right
11
12 class Position(BinaryTree.Position):
13 ”””An abstraction representing the location of a single element.”””
14
15 def init (self, container, node):
16 ”””Constructor should not be invoked by user.”””
17 self. container = container
18 self. node = node
19
20 def element(self):
21 ”””Return the element stored at this Position.”””
22 return self. node. element
23
24 def eq (self, other):
25 ”””Return True if other is a Position representing the same location.”””
26 return type(other) is type(self) and other. node is self. node
27
28 def validate(self, p):
29 ”””Return associated node, if position is valid.”””
30 if not isinstance(p, self.Position):
31 raise TypeError( p must be proper Position type )
32 if p. container is not self:
33 raise ValueError( p does not belong to this container )
34 if p. node. parent is p. node: # convention for deprecated nodes
35 raise ValueError( p is no longer valid )
36 return p. node
37
38 def make position(self, node):
39 ”””Return Position instance for given node (or None if no node).”””
40 return self.Position(self, node) if node is not None else None
Code Fragment 8.8: The beginning of our LinkedBinaryTree class (continued in
Code Fragments 8.9 through 8.11).

www.it-ebooks.info
8.3. Implementing Trees 321
41 #-------------------------- binary tree constructor --------------------------
42 def init (self):
43 ”””Create an initially empty binary tree.”””
44 self. root = None
45 self. size = 0
46
47 #-------------------------- public accessors --------------------------
48 def len (self):
49 ”””Return the total number of elements in the tree.”””
50 return self. size
51
52 def root(self):
53 ”””Return the root Position of the tree (or None if tree is empty).”””
54 return self. make position(self. root)
55
56 def parent(self, p):
57 ”””Return the Position of p s parent (or None if p is root).”””
58 node = self. validate(p)
59 return self. make position(node. parent)
60
61 def left(self, p):
62 ”””Return the Position of p s left child (or None if no left child).”””
63 node = self. validate(p)
64 return self. make position(node. left)
65
66 def right(self, p):
67 ”””Return the Position of p s right child (or None if no right child).”””
68 node = self. validate(p)
69 return self. make position(node. right)
70
71 def num children(self, p):
72 ”””Return the number of children of Position p.”””
73 node = self. validate(p)
74 count = 0
75 if node. left is not None: # left child exists
76 count += 1
77 if node. right is not None: # right child exists
78 count += 1
79 return count
Code Fragment 8.9: Public accessors for our LinkedBinaryTree class. The class
begins in Code Fragment 8.8 and continues in Code Fragments 8.10 and 8.11.

www.it-ebooks.info
322 Chapter 8. Trees
80 def add root(self, e):
81 ”””Place element e at the root of an empty tree and return new Position.
82
83 Raise ValueError if tree nonempty.
84 ”””
85 if self. root is not None: raise ValueError( Root exists )
86 self. size = 1
87 self. root = self. Node(e)
88 return self. make position(self. root)
89
90 def add left(self, p, e):
91 ”””Create a new left child for Position p, storing element e.
92
93 Return the Position of new node.
94 Raise ValueError if Position p is invalid or p already has a left child.
95 ”””
96 node = self. validate(p)
97 if node. left is not None: raise ValueError( Left child exists )
98 self. size += 1
99 node. left = self. Node(e, node) # node is its parent
100 return self. make position(node. left)
101
102 def add right(self, p, e):
103 ”””Create a new right child for Position p, storing element e.
104
105 Return the Position of new node.
106 Raise ValueError if Position p is invalid or p already has a right child.
107 ”””
108 node = self. validate(p)
109 if node. right is not None: raise ValueError( Right child exists )
110 self. size += 1
111 node. right = self. Node(e, node) # node is its parent
112 return self. make position(node. right)
113
114 def replace(self, p, e):
115 ”””Replace the element at position p with e, and return old element.”””
116 node = self. validate(p)
117 old = node. element
118 node. element = e
119 return old
Code Fragment 8.10: Nonpublic update methods for the LinkedBinaryTree class
(continued in Code Fragment 8.11).

www.it-ebooks.info
8.3. Implementing Trees 323
120 def delete(self, p):
121 ”””Delete the node at Position p, and replace it with its child, if any.
122
123 Return the element that had been stored at Position p.
124 Raise ValueError if Position p is invalid or p has two children.
125 ”””
126 node = self. validate(p)
127 if self.num children(p) == 2: raise ValueError( p has two children )
128 child = node. left if node. left else node. right # might be None
129 if child is not None:
130 child. parent = node. parent # child s grandparent becomes parent
131 if node is self. root:
132 self. root = child # child becomes root
133 else:
134 parent = node. parent
135 if node is parent. left:
136 parent. left = child
137 else:
138 parent. right = child
139 self. size −= 1
140 node. parent = node # convention for deprecated node
141 return node. element
142
143 def attach(self, p, t1, t2):
144 ”””Attach trees t1 and t2 as left and right subtrees of external p.”””
145 node = self. validate(p)
146 if not self.is leaf(p): raise ValueError( position must be leaf )
147 if not type(self) is type(t1) is type(t2): # all 3 trees must be same type
148 raise TypeError( Tree types must match )
149 self. size += len(t1) + len(t2)
150 if not t1.is empty( ): # attached t1 as left subtree of node
151 t1. root. parent = node
152 node. left = t1. root
153 t1. root = None # set t1 instance to empty
154 t1. size = 0
155 if not t2.is empty( ): # attached t2 as right subtree of node
156 t2. root. parent = node
157 node. right = t2. root
158 t2. root = None # set t2 instance to empty
159 t2. size = 0
Code Fragment 8.11: Nonpublic update methods for the LinkedBinaryTree class
(continued from Code Fragment 8.10).

www.it-ebooks.info
324 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 Tree and BinaryTree classes:

• The len method, implemented in LinkedBinaryTree, uses an instance variable


storing the number of nodes of T and takes O(1) time. Method is empty,
inherited from Tree, relies on a single call to len and thus takes O(1) time.

• The accessor methods root, left, right, parent, and num children are imple-
mented directly in LinkedBinaryTree and take O(1) time. The sibling and
children methods are derived in BinaryTree based on a constant number of
calls to these other accessors, so they run in O(1) time as well.

• The is root and is leaf methods, from the Tree class, both run in O(1) time,
as is root calls root and then relies on equivalence testing of positions, while
is leaf calls left and right and verifies that None is returned by both.

• 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 various update methods add root, add left, add right, replace, delete,
and attach (that is, their nonpublic implementations) each run in O(1) time,
as they involve relinking only a constant number of nodes per operation.

Table 8.1 summarizes the performance of the linked structure implementation of a


binary tree.

Operation Running Time


len, is empty O(1)
root, parent, left, right, sibling, children, num children O(1)
is root, is leaf O(1)
depth(p) O(d p + 1)
height O(n)
add root, add left, add right, replace, delete, attach O(1)

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 325

8.3.2 Array-Based Representation of a Binary Tree


An alternative representation of a binary tree T is based on a way of numbering the
positions of T . For every position p of T , let f (p) be the integer defined as follows.
• If p is the root of T , then f (p) = 0.
• If p is the left child of position q, then f (p) = 2 f (q) + 1.
• If p is the right child of position q, then f (p) = 2 f (q) + 2.
The numbering function f is known as a level numbering of the positions in a
binary tree T , for it numbers the positions on each level of T in increasing order
from left to right. (See Figure 8.12.) Note well that the level numbering is based
on potential positions within the tree, not actual positions of a given tree, so they
are not necessarily consecutive. For example, in Figure 8.12(b), there are no nodes
with level numbering 13 or 14, because the node with level numbering 6 has no
children.
0

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.12: Binary tree level numbering: (a) general scheme; (b) an example.

www.it-ebooks.info
326 Chapter 8. Trees
The level numbering function f suggests a representation of a binary tree T
by means of an array-based structure A (such as a Python list), 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.13.
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

Figure 8.13: Representation of a binary tree by means of an array.

One advantage of an array-based representation of a binary tree is that a posi-


tion p can be represented by the single integer f (p), and that position-based meth-
ods such as root, parent, left, and right can be implemented using simple arithmetic
operations on the number f (p). Based on our formula for the level numbering, the
left child of p has index 2 f (p) + 1, the right child of p has index 2 f (p) + 2, and
the parent of p has index ( f (p) − 1)/2. We leave the details of a complete im-
plementation as an exercise (R-8.18).
The space usage of an array-based representation depends greatly on the shape
of the tree. Let n be the number of nodes of T , and let fM be the maximum value
of f (p) over all the nodes of T . The array A requires length N = 1 + fM , since
elements range from A[0] to A[ fM ]. Note that A may have a number of empty cells
that do not refer to existing nodes of T . In fact, in the worst case, N = 2n − 1,
the justification of which is left as an exercise (R-8.16). In Section 9.3, we will
see a class of binary trees, called “heaps” for which N = n. Thus, in spite of the
worst-case space usage, there are applications for which the array representation
of a binary tree is space efficient. Still, for general binary trees, the exponential
worst-case space requirement of this representation is prohibitive.
Another drawback of an array representation is that some update operations for
trees cannot be efficiently supported. For example, deleting a node and promoting
its child takes O(n) time because it is not just the child that moves locations within
the array, but all descendants of that child.

www.it-ebooks.info
8.3. Implementing Trees 327

8.3.3 Linked Structure for General Trees


When representing a binary tree with a linked structure, each node explicitly main-
tains fields left and right as references to individual children. For a general tree,
there is no a priori limit on the number of children that a node may have. A natural
way to realize a general tree T as a linked structure is to have each node store a
single container of references to its children. For example, a children field of a
node can be a Python list of references to the children of the node (if any). Such a
linked representation is schematically illustrated in Figure 8.14.

New York
parent

element

Baltimore Chicago Providence Seattle


children
(a) (b)

Figure 8.14: 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 summarizes the performance of the implementation of a general tree


using a linked structure. The analysis is left as an exercise (R-8.14), but we note
that, by using a collection to store the children of each position p, we can implement
children(p) by simply iterating that collection.

Operation Running Time


len, is empty O(1)
root, parent, is root, is leaf O(1)
children(p) O(c p + 1)
depth(p) O(d p + 1)
height O(n)

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. The space usage is O(n).

www.it-ebooks.info
328 Chapter 8. Trees

8.4 Tree Traversal Algorithms


A traversal of a tree T is a systematic way of accessing, or “visiting,” all the posi-
tions of T . The specific action associated with the “visit” of a position p depends
on the application of this traversal, and could involve anything from increment-
ing a counter to performing some complex computation for p. In this section, we
describe several common traversal schemes for trees, implement them in the con-
text of our various tree classes, and discuss several common applications of tree
traversals.

8.4.1 Preorder and Postorder Traversals of General Trees


In a preorder traversal of a tree T , the root of T is visited first and then the sub-
trees rooted at its children are traversed recursively. If the tree is ordered, then
the subtrees are traversed according to the order of the children. The pseudo-code
for the preorder traversal of the subtree rooted at a position p is shown in Code
Fragment 8.12.

Algorithm preorder(T, p):


perform the “visit” action for position p
for each child c in T.children(p) do
preorder(T, 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 T.

Figure 8.15 portrays the order in which positions of a sample tree are visited
during an application of the preorder traversal algorithm.

Paper

Title Abstract §1 §2 §3 References

§ 1.1 § 1.2 § 2.1 § 2.2 § 2.3 § 3.1 § 3.2

Figure 8.15: 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 329
Postorder Traversal
Another important tree traversal algorithm is the postorder traversal. In some
sense, this algorithm can be viewed as the opposite of the preorder traversal, be-
cause it recursively traverses the subtrees rooted at the children of the root first, and
then visits the root (hence, the name “postorder”). Pseudo-code for the postorder
traversal is given in Code Fragment 8.13, and an example of a postorder traversal
is portrayed in Figure 8.16.

Algorithm postorder(T, p):


for each child c in T.children(p) do
postorder(T, c) {recursively traverse the subtree rooted at c}
perform the “visit” action for position p

Code Fragment 8.13: Algorithm postorder for performing the postorder traversal of
a subtree rooted at position p of a tree T.

Paper

Title Abstract §1 §2 §3 References

§ 1.1 § 1.2 § 2.1 § 2.2 § 2.3 § 3.1 § 3.2

Figure 8.16: Postorder traversal of the ordered tree of Figure 8.15.

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 height2, 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.5, 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 the n positions of the tree.

www.it-ebooks.info
330 Chapter 8. Trees

8.4.2 Breadth-First Tree Traversal


Although the preorder and postorder traversals are common ways of visiting the
positions of a tree, another common approach is to traverse a tree so that we visit
all the positions at depth d before we visit the positions at depth d + 1. Such an
algorithm is known as a breadth-first traversal.
A breadth-first traversal is a common approach used in software for playing
games. A game tree represents the possible choices of moves that might be made
by a player (or computer) during a game, with the root of the tree being the initial
configuration for the game. For example, Figure 8.17 displays a partial game tree
for Tic-Tac-Toe.
1

2 3X X
4
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.17: Partial game tree for Tic-Tac-Toe, with annotations displaying the or-
der in which positions are visited in a breadth-first traversal.

A breadth-first traversal of such a game tree is often performed because a computer


may be unable to explore a complete game tree in a limited amount of time. So the
computer will consider all moves, then responses to those moves, going as deep as
computational time allows.
Pseudo-code for a breadth-first traversal is given in Code Fragment 8.14. The
process is not recursive, since we are not traversing entire subtrees at once. We use
a queue to produce a FIFO (i.e., first-in first-out) semantics for the order in which
we visit nodes. The overall running time is O(n), due to the n calls to enqueue and
n calls to dequeue.

Algorithm breadthfirst(T):
Initialize queue Q to contain T.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 T.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 331

8.4.3 Inorder Traversal of a Binary Tree


The standard preorder, postorder, and breadth-first traversals that were introduced
for general trees, can be directly applied to binary trees. In this section, we intro-
duce another common traversal algorithm specifically for a binary tree.
During an inorder traversal, we visit a position between the recursive traver-
sals of its left and right subtrees. The inorder traversal of a binary tree T can be
informally viewed as visiting the nodes of T “from left to right.” Indeed, for every
position p, the inorder traversal visits p after all the positions in the left subtree of
p and before all the positions in the right subtree of p. Pseudo-code for the inorder
traversal algorithm is given in Code Fragment 8.15, and an example of an inorder
traversal is portrayed in Figure 8.18.

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}

Code Fragment 8.15: Algorithm inorder for performing an inorder traversal of a


subtree rooted at position p of a binary tree.

/ +

× + × 6

+ 3 − 2 3 −

3 1 9 5 7 4

Figure 8.18: Inorder traversal of a binary tree.

The inorder traversal algorithm has several important applications. When using
a binary tree to represent an arithmetic expression, as in Figure 8.18, 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
332 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 binary tree T
such that, for each 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.19. 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.19: A binary search tree storing integers. The solid path is traversed when
searching (successfully) for 36. 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 an empty subtree, the search
terminates unsuccessfully. In other words, a binary search tree can be viewed as a
binary decision tree (recall Example 8.6), where the question asked at each internal
node is whether the element 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.19.
Note that the running time of searching in a binary search tree T is proportional
to the height of T . Recall from Proposition 8.8 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 333

8.4.4 Implementing Tree Traversals in Python


When first defining the tree ADT in Section 8.1.2, we stated that tree T should
include support for the following methods:
T.positions( ): Generate an iteration of all positions of tree T.
iter(T): Generate an iteration of all elements stored within tree T.
At that time, we did not make any assumption about the order in which these
iterations report their results. In this section, we demonstrate how any of the tree
traversal algorithms we have introduced could be used to produce these iterations.
To begin, we note that it is easy to produce an iteration of all elements of a
tree, if we rely on a presumed iteration of all positions. Therefore, support for
the iter(T) syntax can be formally provided by a concrete implementation of the
special method iter within the abstract base class Tree. We rely on Python’s
generator syntax as the mechanism for producing iterations. (See Section 1.8.) Our
implementation of Tree. iter is given in Code Fragment 8.16.
75 def iter (self):
76 ”””Generate an iteration of the tree s elements.”””
77 for p in self.positions( ): # use same order as positions()
78 yield p.element( ) # but yield each element
Code Fragment 8.16: Iterating all elements of a Tree instance, based upon an iter-
ation of the positions of the tree. This code should be included in the body of the
Tree class.

To implement the positions method, we have a choice of tree traversal algo-


rithms. Given that there are advantages to each of those traversal orders, we will
provide independent implementations of each strategy that can be called directly
by a user of our class. We can then trivially adapt one of those as a default order
for the positions method of the tree ADT.

Preorder Traversal
We begin by considering the preorder traversal algorithm. We will support a public
method with calling signature T.preorder( ) for tree T, which generates a preorder
iteration of all positions within the tree. However, the recursive algorithm for gen-
erating a preorder traversal, as originally described in Code Fragment 8.12, must
be parameterized by a specific position within the tree that serves as the root of a
subtree to traverse. A standard solution for such a circumstance is to define a non-
public utility method with the desired recursive parameterization, and then to have
the public method preorder invoke the nonpublic method upon the root of the tree.
Our implementation of such a design is given in Code Fragment 8.17.

www.it-ebooks.info
334 Chapter 8. Trees
79 def preorder(self):
80 ”””Generate a preorder iteration of positions in the tree.”””
81 if not self.is empty( ):
82 for p in self. subtree preorder(self.root( )): # start recursion
83 yield p
84
85 def subtree preorder(self, p):
86 ”””Generate a preorder iteration of positions in subtree rooted at p.”””
87 yield p # visit p before its subtrees
88 for c in self.children(p): # for each child c
89 for other in self. subtree preorder(c): # do preorder of c’s subtree
90 yield other # yielding each to our caller
Code Fragment 8.17: Support for performing a preorder traversal of a tree. This
code should be included in the body of the Tree class.

Formally, both preorder and the utility subtree preorder are generators. Rather
than perform a “visit” action from within this code, we yield each position to the
caller and let the caller decide what action to perform at that position.
The subtree preorder method is the recursive one. However, because we are
relying on generators rather than traditional functions, the recursion has a slightly
different form. In order to yield all positions within the subtree of child c, we loop
over the positions yielded by the recursive call self. subtree preorder(c), and re-
yield each position in the outer context. Note that if p is a leaf, the for loop over
self.children(p) is trivial (this is the base case for our recursion).
We rely on a similar technique in the public preorder method to re-yield all
positions that are generated by the recursive process starting at the root of the tree;
if the tree is empty, nothing is yielded. At this point, we have provided full support
for the preorder generator. A user of the class can therefore write code such as
for p in T.preorder( ):
# ”visit” position p
The official tree ADT requires that all trees support a positions method as well. To
use a preorder traversal as the default order of iteration, we include the definition
shown in Code Fragment 8.18 within our Tree class. Rather than loop over the
results returned by the preorder call, we return the entire iteration as an object.

91 def positions(self):
92 ”””Generate an iteration of the tree s positions.”””
93 return self.preorder( ) # return entire preorder iteration
Code Fragment 8.18: An implementation of the positions method for the Tree class
that relies on a preorder traversal to generate the results.

www.it-ebooks.info
8.4. Tree Traversal Algorithms 335
Postorder Traversal
We can implement a postorder traversal using very similar technique as with a
preorder traversal. The only difference is that within the recursive utility for a post-
order we wait to yield position p until after we have recursively yield the positions
in its subtrees. An implementation is given in Code Fragment 8.19.

94 def postorder(self):
95 ”””Generate a postorder iteration of positions in the tree.”””
96 if not self.is empty( ):
97 for p in self. subtree postorder(self.root( )): # start recursion
98 yield p
99
100 def subtree postorder(self, p):
101 ”””Generate a postorder iteration of positions in subtree rooted at p.”””
102 for c in self.children(p): # for each child c
103 for other in self. subtree postorder(c): # do postorder of c’s subtree
104 yield other # yielding each to our caller
105 yield p # visit p after its subtrees
Code Fragment 8.19: Support for performing a postorder traversal of a tree. This
code should be included in the body of the Tree class.

Breadth-First Traversal
In Code Fragment 8.20, we provide an implementation of the breadth-first traversal
algorithm in the context of our Tree class. Recall that the breadth-first traversal
algorithm is not recursive; it relies on a queue of positions to manage the traver-
sal process. Our implementation uses the LinkedQueue class from Section 7.1.2,
although any implementation of the queue ADT would suffice.

Inorder Traversal for Binary Trees


The preorder, postorder, and breadth-first traversal algorithms are applicable to
all trees, and so we include their implementations within the Tree abstract base
class. Those methods are inherited by the abstract BinaryTree class, the concrete
LinkedBinaryTree class, and any other dependent tree classes we might develop.
The inorder traversal algorithm, because it explicitly relies on the notion of a
left and right child of a node, only applies to binary trees. We therefore include its
definition within the body of the BinaryTree class. We use a similar technique to
implement an inorder traversal (Code Fragment 8.21) as we did with preorder and
postorder traversals.

www.it-ebooks.info
336 Chapter 8. Trees
106 def breadthfirst(self):
107 ”””Generate a breadth-first iteration of the positions of the tree.”””
108 if not self.is empty( ):
109 fringe = LinkedQueue( ) # known positions not yet yielded
110 fringe.enqueue(self.root( )) # starting with the root
111 while not fringe.is empty( ):
112 p = fringe.dequeue( ) # remove from front of the queue
113 yield p # report this position
114 for c in self.children(p):
115 fringe.enqueue(c) # add children to back of queue
Code Fragment 8.20: An implementation of a breadth-first traversal of a tree. This
code should be included in the body of the Tree class.
37 def inorder(self):
38 ”””Generate an inorder iteration of positions in the tree.”””
39 if not self.is empty( ):
40 for p in self. subtree inorder(self.root( )):
41 yield p
42
43 def subtree inorder(self, p):
44 ”””Generate an inorder iteration of positions in subtree rooted at p.”””
45 if self.left(p) is not None: # if left child exists, traverse its subtree
46 for other in self. subtree inorder(self.left(p)):
47 yield other
48 yield p # visit p between its subtrees
49 if self.right(p) is not None: # if right child exists, traverse its subtree
50 for other in self. subtree inorder(self.right(p)):
51 yield other
Code Fragment 8.21: Support for performing an inorder traversal of a binary tree.
This code should be included in the BinaryTree class (given in Code Fragment 8.7).

For many applications of binary trees, an inorder traversal provides a natural


iteration. We could make it the default for the BinaryTree class by overriding the
positions method that was inherited from the Tree class (see Code Fragment 8.22).

52 # override inherited version to make inorder the default


53 def positions(self):
54 ”””Generate an iteration of the tree s positions.”””
55 return self.inorder( ) # make inorder the default
Code Fragment 8.22: Defining the BinaryTree.position method so that positions are
reported using inorder traversal.

www.it-ebooks.info
8.4. Tree Traversal Algorithms 337

8.4.5 Applications of Tree Traversals


In this section, we demonstrate several representative applications of tree traversals,
including some customizations of the standard traversal algorithms.

Table of Contents
When using a tree to represent the hierarchical structure of a document, a preorder
traversal of the tree can naturally be used to produce a table of contents for the doc-
ument. For example, the table of contents associated with the tree from Figure 8.15
is displayed in Figure 8.20. Part (a) of that figure gives a simple presentation with
one element per line; part (b) shows a more attractive presentation produced by
indenting each element based on its depth within the tree. A similar presentation
could be used to display the contents of a computer’s file system, based on its tree
representation (as in Figure 8.3).

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.20: Table of contents for a document represented by the tree in Figure 8.15:
(a) without indentation; (b) with indentation based on depth within the tree.

The unindented version of the table of contents, given a tree T , can be produced
with the following code:
for p in T.preorder( ):
print(p.element( ))
To produce the presentation of Figure 8.20(b), we indent each element with a
number of spaces equal to twice the element’s depth in the tree (hence, the root ele-
ment was unindented). Although we could replace the body of the above loop with
the statement print(2 T.depth(p) + str(p.element( ))), such an approach is
unnecessarily inefficient. Although 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 hid-
den 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 height1 in Section 8.1.3.

www.it-ebooks.info
338 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 def preorder indent(T, p, d):
2 ”””Print preorder representation of subtree of T rooted at p at depth d.”””
3 print(2 d + str(p.element( ))) # use depth for indentation
4 for c in T.children(p):
5 preorder indent(T, c, d+1) # child depth is d+1
Code Fragment 8.23: Efficient recursion for printing indented version of a pre-
order traversal. On a complete tree T , the recursion should be started with form
preorder indent(T, T.root( ), 0).

In the example of Figure 8.20, 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 index of each position, relative to
its siblings, along the path from the root to the current position. To accomplish the
task, we add a representation of that path as an additional parameter to the recursive
signature. Specifically, we use a list of zero-indexed numbers, one for each position
along the downward path, other than the root. (We convert those numbers to one-
indexed form when printing.)
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 share 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,” that same block
of code must remove the extraneous entry from the list before completing its task.
An implementation based on this approach is given in Code Fragment 8.24.

www.it-ebooks.info
8.4. Tree Traversal Algorithms 339
1 def preorder label(T, p, d, path):
2 ”””Print labeled representation of subtree of T rooted at p at depth d.”””
3 label = . .join(str(j+1) for j in path) # displayed labels are one-indexed
4 print(2 d + label, p.element( ))
5 path.append(0) # path entries are zero-indexed
6 for c in T.children(p):
7 preorder label(T, c, d+1, path) # child depth is d+1
8 path[−1] += 1
9 path.pop( )
Code Fragment 8.24: Efficient recursion for printing an indented and labeled pre-
sentation of a preorder traversal.

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.20(a). 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 as follows. If T consists of a
single position p, then
P(T ) = str(p.element()).
Otherwise, it is defined recursively as,

P(T ) = str(p.element()) + ( + 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):
Electronics R’Us (R&D, Sales (Domestic, International (Canada,
S. America, Overseas (Africa, Europe, Asia, Australia))),
Purchasing, Manufacturing (TV, CD, Tuner))
Although the parenthetic representation is essentially a preorder traversal, we
cannot easily produce the additional punctuation using the formal implementation
of preorder, as given in Code Fragment 8.17. The opening parenthesis must be
produced just before the loop over a position’s children and the closing parenthesis
must be produced just after that loop. Furthermore, the separating commas must
be produced. The Python function parenthesize, shown in Code Fragment 8.25, is
a custom traversal that prints such a parenthetic string representation of a tree T .

www.it-ebooks.info
340 Chapter 8. Trees
1 def parenthesize(T, p):
2 ”””Print parenthesized representation of subtree of T rooted at p.”””
3 print(p.element( ), end= ) # use of end avoids trailing newline
4 if not T.is leaf(p):
5 first time = True
6 for c in T.children(p):
7 sep = ( if first time else , # determine proper separator
8 print(sep, end= )
9 first time = False # any future passes will not be the first
10 parenthesize(T, c) # recur on child
11 print( ) , end= ) # include closing parenthesis
Code Fragment 8.25: Function that prints parenthetic string representation of a tree.

Computing Disk Space


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 4, we specifically
examined the topic of file systems (see Section 4.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 4.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.19 does not suffice for
this purpose. As it visits the position of a directory, there is no easy way to discern
which of the previous positions represent children of that directory, nor how much
recursive disk space was allocated.
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 prob-
lem, with each level of recursion providing a return value to the (parent) caller, is
provided in Code Fragment 8.26.
1 def disk space(T, p):
2 ”””Return total disk space for subtree of T rooted at p.”””
3 subtotal = p.element( ).space( ) # space used at position p
4 for c in T.children(p):
5 subtotal += disk space(T, c) # add child’s space to subtotal
6 return subtotal
Code Fragment 8.26: Recursive computation of disk space for a tree. We assume
that a space( ) method of each tree element reports the local space used at that
position.

www.it-ebooks.info
8.4. Tree Traversal Algorithms 341

8.4.6 Euler Tours and the Template Method Pattern 


The various applications described in Section 8.4.5 demonstrate the great power
of recursive tree traversals. Unfortunately, they also show that the specific imple-
mentations of the preorder and postorder methods of our Tree class, or the inorder
method of the BinaryTree class, are not general enough to capture the range of
computations we desire. In some cases, we need more of a blending of the ap-
proaches, with initial work performed before recurring on subtrees, additional work
performed after those recursions, and in the case of a binary tree, work performed
between the two possible recursions. Furthermore, in some contexts it was impor-
tant to know the depth of a position, or the complete path from the root to that
position, or to return information from one level of the recursion to another. For
each of the previous applications, we were able to develop a custom implementa-
tion to properly adapt the recursive ideas, but the great principles of object-oriented
programming introduced in Section 2.1.1 include adaptability and reusability.
In this section, we develop a more general framework for implementing tree
traversals based on a concept known as an Euler tour traversal. The Euler tour
traversal of a general tree T can be informally defined as a “walk” around T , where
we start by going from the root toward its leftmost child, viewing the edges of T as
being “walls” that we always keep to our left. (See Figure 8.21.)

/ +

× + × 6

+ 3 − 2 3 −

3 1 9 5 7 4

Figure 8.21: Euler tour traversal of a tree.

The complexity of the walk is O(n), because it progresses 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 think of 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.

www.it-ebooks.info
342 Chapter 8. Trees
The process of an Euler tour can easily be viewed recursively. 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.21 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. The pseudo-code for an Euler tour
traversal of a subtree rooted at a position p is shown in Code Fragment 8.27.

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.27: Algorithm eulertour for performing an Euler tour traversal of
a subtree rooted at position p of a tree.

The Template Method Pattern


To provide a framework that is reusable and adaptable, we rely on an interesting
object-oriented software design pattern, the template method pattern. The template
method pattern describes a generic computation mechanism that can be specialized
for a particular application by redefining certain steps. To allow customization, the
primary algorithm calls auxiliary functions known as hooks at designated steps of
the process.
In the context of an Euler tour traversal, we define two separate hooks, a pre-
visit hook that is called before the subtrees are traversed, and a postvisit hook that is
called after the completion of the subtree traversals. Our implementation will take
the form of an EulerTour class that manages the process, and defines trivial defi-
nitions for the hooks that do nothing. The traversal can be customized by defining
a subclass of EulerTour and overriding one or both hooks to provide specialized
behavior.

Python Implementation
Our implementation of an EulerTour class is provided in Code Fragment 8.28. The
primary recursive process is defined in the nonpublic tour method. A tour instance
is created by sending a reference to a specific tree to the constructor, and then by
calling the public execute method, which beings the tour and returns a final result
of the computation.

www.it-ebooks.info
8.4. Tree Traversal Algorithms 343
1 class EulerTour:
2 ”””Abstract base class for performing Euler tour of a tree.
3
4 hook previsit and hook postvisit may be overridden by subclasses.
5 ”””
6 def init (self, tree):
7 ”””Prepare an Euler tour template for given tree.”””
8 self. tree = tree
9
10 def tree(self):
11 ”””Return reference to the tree being traversed.”””
12 return self. tree
13
14 def execute(self):
15 ”””Perform the tour and return any result from post visit of root.”””
16 if len(self. tree) > 0:
17 return self. tour(self. tree.root( ), 0, [ ]) # start the recursion
18
19 def tour(self, p, d, path):
20 ”””Perform tour of subtree rooted at Position p.
21
22 p Position of current node being visited
23 d depth of p in the tree
24 path list of indices of children on path from root to p
25 ”””
26 self. hook previsit(p, d, path) # ”pre visit” p
27 results = [ ]
28 path.append(0) # add new index to end of path before recursion
29 for c in self. tree.children(p):
30 results.append(self. tour(c, d+1, path)) # recur on child s subtree
31 path[−1] += 1 # increment index
32 path.pop( ) # remove extraneous index from end of path
33 answer = self. hook postvisit(p, d, path, results) # ”post visit” p
34 return answer
35
36 def hook previsit(self, p, d, path): # can be overridden
37 pass
38
39 def hook postvisit(self, p, d, path, results): # can be overridden
40 pass
Code Fragment 8.28: An EulerTour base class providing a framework for perform-
ing Euler tour traversals of a tree.

www.it-ebooks.info
344 Chapter 8. Trees
Based on our experience of customizing traversals for sample applications Sec-
tion 8.4.5, we build support into the primary EulerTour for maintaining the re-
cursive depth and the representation of the recursive path through a tree, using the
approach that we introduced in Code Fragment 8.24. We also provide a mechanism
for one recursive level to return a value to another when post-processing. Formally,
our framework relies on the following two hooks that can be specialized:

• method hook previsit(p, d, path)


This function is called once for each position, immediately before its subtrees
(if any) are traversed. Parameter p is a position in the tree, d is the depth of
that position, and path is a list of indices, using the convention described in
the discussion of Code Fragment 8.24. No return value is expected from this
function.
• method hook postvisit(p, d, path, results)
This function is called once for each position, immediately after its subtrees
(if any) are traversed. The first three parameters use the same convention as
did hook previsit. The final parameter is a list of objects that were provided
as return values from the post visits of the respective subtrees of p. Any value
returned by this call will be available to the parent of p during its postvisit.
For more complex tasks, subclasses of EulerTour may also choose to initialize
and maintain additional state in the form of instance variables that can be accessed
within the bodies of the hooks.

Using the Euler Tour Framework


To demonstrate the flexibility of our Euler tour framework, we revisit the sample
applications from Section 8.4.5. As a simple example, an indented preorder traver-
sal, akin to that originally produced by Code Fragment 8.23, can be generated with
the simple subclass given in Code Fragment 8.29.
1 class PreorderPrintIndentedTour(EulerTour):
2 def hook previsit(self, p, d, path):
3 print(2 d + str(p.element( )))
Code Fragment 8.29: A subclass of EulerTour that produces an indented preorder
list of a tree’s elements.

Such a tour would be started by creating an instance of the subclass for a given
tree T, and invoking its execute method. This could be expressed as follows:
tour = PreorderPrintIndentedTour(T)
tour.execute( )

www.it-ebooks.info
8.4. Tree Traversal Algorithms 345
A labeled version of an indented, preorder presentation, akin to Code Frag-
ment 8.24, could be generated by the new subclass of EulerTour shown in Code
Fragment 8.30.
1 class PreorderPrintIndentedLabeledTour(EulerTour):
2 def hook previsit(self, p, d, path):
3 label = . .join(str(j+1) for j in path) # labels are one-indexed
4 print(2 d + label, p.element( ))
Code Fragment 8.30: A subclass of EulerTour that produces a labeled and indented,
preorder list of a tree’s elements.

To produce the parenthetic string representation, originally achieved with Code


Fragment 8.25, we define a subclass that overrides both the previsit and postvisit
hooks. Our new implementation is given in Code Fragment 8.31.
1 class ParenthesizeTour(EulerTour):
2 def hook previsit(self, p, d, path):
3 if path and path[−1] > 0: # p follows a sibling
4 print( , , end= ) # so preface with comma
5 print(p.element( ), end= ) # then print element
6 if not self.tree( ).is leaf(p): # if p has children
7 print( ( , end= ) # print opening parenthesis
8
9 def hook postvisit(self, p, d, path, results):
10 if not self.tree( ).is leaf(p): # if p has children
11 print( ) , end= ) # print closing parenthesis
Code Fragment 8.31: A subclass of EulerTour that prints a parenthetic string repre-
sentation of a tree.

Notice that in this implementation, we need to invoke a method on the tree instance
that is being traversed from within the hooks. The public tree( ) method of the
EulerTour class serves as an accessor for that tree.
Finally, the task of computing disk space, as originally implemented in Code
Fragment 8.26, can be performed quite easily with the EulerTour subclass shown
in Code Fragment 8.32. The postvisit result of the root will be returned by the call
to execute( ).
1 class DiskSpaceTour(EulerTour):
2 def hook postvisit(self, p, d, path, results):
3 # we simply add space associated with p to that of its subtrees
4 return p.element( ).space( ) + sum(results)
Code Fragment 8.32: A subclass of EulerTour that computes disk space for a tree.

www.it-ebooks.info
346 Chapter 8. Trees

The Euler Tour Traversal of a Binary Tree


In Section 8.4.6, we introduced the concept of an Euler tour traversal of a general
graph, using the template method pattern in designing the EulerTour class. That
class provided methods hook previsit and hook postvisit that could be overrid-
den to customize a tour. In Code Fragment 8.33 we provide a BinaryEulerTour
specialization that includes an additional hook invisit that is called once for each
position—after its left subtree is traversed, but before its right subtree is traversed.
Our implementation of BinaryEulerTour replaces the original tour utility to
specialize to the case in which a node has at most two children. If a node has only
one child, a tour differentiates between whether that is a left child or a right child,
with the “in visit” taking place after the visit of a sole left child, but before the visit
of a sole right child. In the case of a leaf, the three hooks are called in succession.

1 class BinaryEulerTour(EulerTour):
2 ”””Abstract base class for performing Euler tour of a binary tree.
3
4 This version includes an additional hook invisit that is called after the tour
5 of the left subtree (if any), yet before the tour of the right subtree (if any).
6
7 Note: Right child is always assigned index 1 in path, even if no left sibling.
8 ”””
9 def tour(self, p, d, path):
10 results = [None, None] # will update with results of recursions
11 self. hook previsit(p, d, path) # ”pre visit” for p
12 if self. tree.left(p) is not None: # consider left child
13 path.append(0)
14 results[0] = self. tour(self. tree.left(p), d+1, path)
15 path.pop( )
16 self. hook invisit(p, d, path) # ”in visit” for p
17 if self. tree.right(p) is not None: # consider right child
18 path.append(1)
19 results[1] = self. tour(self. tree.right(p), d+1, path)
20 path.pop( )
21 answer = self. hook postvisit(p, d, path, results) # ”post visit” p
22 return answer
23
24 def hook invisit(self, p, d, path): pass # can be overridden
Code Fragment 8.33: A BinaryEulerTour base class providing a specialized tour for
binary trees. The original EulerTour base class was given in Code Fragment 8.28.

www.it-ebooks.info
8.4. Tree Traversal Algorithms 347

4
0 1 2 3 4 5 6 7 8 9 10 11 12
Figure 8.22: An inorder drawing of a binary tree.

To demonstrate use of the BinaryEulerTour framework, we develop a subclass


that computes a graphical layout of a binary tree, as shown in Figure 8.22. The
geometry is determined by an algorithm that assigns x- and y-coordinates to each
position p of a binary tree T using the following two rules:
• x(p) is the number of positions visited before p in an inorder traversal of T .
• y(p) is the depth of p in T .
In this application, we take the convention common in computer graphics that x-
coordinates increase left to right and y-coordinates increase top to bottom. So the
origin is in the upper left corner of the computer screen.
Code Fragment 8.34 provides an implementation of a BinaryLayout subclass
that implements the above algorithm for assigning (x, y) coordinates to the element
stored at each position of a binary tree. We adapt the BinaryEulerTour framework
by introducing additional state in the form of a count instance variable that repre-
sents the number of “in visits” that we have performed. The x-coordinate for each
position is set according to that counter.
1 class BinaryLayout(BinaryEulerTour):
2 ”””Class for computing (x,y) coordinates for each node of a binary tree.”””
3 def init (self, tree):
4 super( ). init (tree) # must call the parent constructor
5 self. count = 0 # initialize count of processed nodes
6
7 def hook invisit(self, p, d, path):
8 p.element( ).setX(self. count) # x-coordinate serialized by count
9 p.element( ).setY(d) # y-coordinate is depth
10 self. count += 1 # advance count of processed nodes
Code Fragment 8.34: A BinaryLayout class that computes coordinates at which to
draw positions of a binary tree. We assume that the element type for the original
tree supports setX and setY methods.

www.it-ebooks.info
348 Chapter 8. Trees

8.5 Case Study: An Expression Tree


In Example 8.7, we introduced the use of a binary tree to represent the structure of
an arithmetic expression. In this section, we define a new ExpressionTree class that
provides support for constructing such trees, and for displaying and evaluating the
arithmetic expression that such a tree represents. Our ExpressionTree class is de-
fined as a subclass of LinkedBinaryTree, and we rely on the nonpublic mutators to
construct such trees. Each internal node must store a string that defines a binary op-
erator (e.g., + ), and each leaf must store a numeric value (or a string representing
a numeric value).
Our eventual goal is to build arbitrarily complex expression trees for compound
arithmetic expressions such as (((3 + 1) × 4)/((9 − 5) + 2)). However, it suffices
for the ExpressionTree class to support two basic forms of initialization:
ExpressionTree(value): Create a tree storing the given value at the root.
ExpressionTree(op, E1 , E2 ): Create a tree storing string op at the root (e.g., +),
and with the structures of existing ExpressionTree
instances E1 and E2 as the left and right subtrees of
the root, respectively.
Such a constructor for the ExpressionTree class is given in Code Fragment 8.35.
The class formally inherits from LinkedBinaryTree, so it has access to all the non-
public update methods that were defined in Section 8.3.1. We use add root to cre-
ate an initial root of the tree storing the token provided as the first parameter. Then
we perform run-time checking of the parameters to determine whether the caller
invoked the one-parameter version of the constructor (in which case, we are done),
or the three-parameter form. In that case, we use the inherited attach method to
incorporate the structure of the existing trees as subtrees of the root.

Composing a Parenthesized String Representation


A string representation of an existing expression tree instance, for example, as
(((3+1)x4)/((9-5)+2)) , can be produced by displaying tree elements us-
ing an inorder traversal, but with opening and closing parentheses inserted with
a preorder and postorder step, respectively. In the context of an ExpressionTree
class, we support a special str method (see Section 2.3.2) that returns the
appropriate string. Because it is more efficient to first build a sequence of individ-
ual strings to be joined together (see discussion of “Composing Strings” in Sec-
tion 5.4.2), the implementation of str relies on a nonpublic, recursive method
named parenthesize recur that appends a series of strings to a list. These methods
are included in Code 8.35.

www.it-ebooks.info
8.5. Case Study: An Expression Tree 349

1 class ExpressionTree(LinkedBinaryTree):
2 ”””An arithmetic expression tree.”””
3
4 def init (self, token, left=None, right=None):
5 ”””Create an expression tree.
6
7 In a single parameter form, token should be a leaf value (e.g., 42 ),
8 and the expression tree will have that value at an isolated node.
9
10 In a three-parameter version, token should be an operator,
11 and left and right should be existing ExpressionTree instances
12 that become the operands for the binary operator.
13 ”””
14 super( ). init ( ) # LinkedBinaryTree initialization
15 if not isinstance(token, str):
16 raise TypeError( Token must be a string )
17 self. add root(token) # use inherited, nonpublic method
18 if left is not None: # presumably three-parameter form
19 if token not in +-*x/ :
20 raise ValueError( token must be valid operator )
21 self. attach(self.root( ), left, right) # use inherited, nonpublic method
22
23 def str (self):
24 ”””Return string representation of the expression.”””
25 pieces = [ ] # sequence of piecewise strings to compose
26 self. parenthesize recur(self.root( ), pieces)
27 return .join(pieces)
28
29 def parenthesize recur(self, p, result):
30 ”””Append piecewise representation of p s subtree to resulting list.”””
31 if self.is leaf(p):
32 result.append(str(p.element( ))) # leaf value as a string
33 else:
34 result.append( ( ) # opening parenthesis
35 self. parenthesize recur(self.left(p), result) # left subtree
36 result.append(p.element( )) # operator
37 self. parenthesize recur(self.right(p), result) # right subtree
38 result.append( ) ) # closing parenthesis
Code Fragment 8.35: The beginning of an ExpressionTree class.

www.it-ebooks.info
350 Chapter 8. Trees
Expression Tree Evaluation
The numeric evaluation of an expression tree can be accomplished with a simple
application of a postorder traversal. If we know the values represented by the two
subtrees of an internal position, we can calculate the result of the computation that
position designates. Pseudo-code for the recursive evaluation of the value repre-
sented by a subtree rooted at position p is given in Code Fragment 8.36.

Algorithm evaluate recur(p):


if p is a leaf then
return the value stored at p
else
let ◦ be the operator stored at p
x = evaluate recur(left(p))
y = evaluate recur(right(p))
return x ◦ y
Code Fragment 8.36: Algorithm evaluate recur for evaluating the expression rep-
resented by a subtree of an arithmetic expression tree rooted at position p.

To implement this algorithm in the context of a Python ExpressionTree class,


we provide a public evaluate method that is invoked on instance T as T.evaluate( ).
Code Fragment 8.37 provides such an implementation, relying on a nonpublic
evaluate recur method that computes the value of a designated subtree.

39 def evaluate(self):
40 ”””Return the numeric result of the expression.”””
41 return self. evaluate recur(self.root( ))
42
43 def evaluate recur(self, p):
44 ”””Return the numeric result of subtree rooted at p.”””
45 if self.is leaf(p):
46 return float(p.element( )) # we assume element is numeric
47 else:
48 op = p.element( )
49 left val = self. evaluate recur(self.left(p))
50 right val = self. evaluate recur(self.right(p))
51 if op == + : return left val + right val
52 elif op == - : return left val − right val
53 elif op == / : return left val / right val
54 else: return left val right val # treat x or as multiplication
Code Fragment 8.37: Support for evaluating an ExpressionTree instance.

www.it-ebooks.info
8.5. Case Study: An Expression Tree 351
Building an Expression Tree
The constructor for the ExpressionTree class, from Code Fragment 8.35, provides
basic functionality for combining existing trees to build larger expression trees.
However, the question still remains how to construct a tree that represents an ex-
pression for a given string, such as (((3+1)x4)/((9-5)+2)) .
To automate this process, we rely on a bottom-up construction algorithm, as-
suming that a string can first be tokenized so that multidigit numbers are treated
atomically (see Exercise R-8.30), and that the expression is fully parenthesized.
The algorithm uses a stack S while scanning tokens of the input expression E to
find values, operators, and right parentheses. (Left parentheses are ignored.)
• When we see an operator ◦, we push that string on the stack.
• When we see a literal value v, we create a single-node expression tree T
storing v, and push T on the stack.
• When we see a right parenthesis, ) , we pop the top three items from the
stack S, which represent a subexpression (E1 ◦ E2 ). We then construct a
tree T using trees for E1 and E2 as subtrees of the root storing ◦, and push
the resulting tree T back on the stack.
We repeat this until the expression E has been processed, at which time the top
element on the stack is the expression tree for E. The total running time is O(n).
An implementation of this algorithm is given in Code Fragment 8.38 in the form
of a stand-alone function named build expression tree, which produces and returns
an appropriate ExpressionTree instance, assuming the input has been tokenized.

1 def build expression tree(tokens):


2 ”””Returns an ExpressionTree based upon by a tokenized expression.”””
3 S=[] # we use Python list as stack
4 for t in tokens:
5 if t in +-x*/ : # t is an operator symbol
6 S.append(t) # push the operator symbol
7 elif t not in () : # consider t to be a literal
8 S.append(ExpressionTree(t)) # push trivial tree storing value
9 elif t == ) : # compose a new tree from three constituent parts
10 right = S.pop( ) # right subtree as per LIFO
11 op = S.pop( ) # operator symbol
12 left = S.pop( ) # left subtree
13 S.append(ExpressionTree(op, left, right)) # repush tree
14 # we ignore a left parenthesis
15 return S.pop( )
Code Fragment 8.38: Implementation of a build expression tree that produces an
ExpressionTree from a sequence of tokens representing an arithmetic expression.

www.it-ebooks.info
352 Chapter 8. Trees

8.6 Exercises
For help with exercises, please visit the site, www.wiley.com/college/goodrich.

Reinforcement
R-8.1 The following questions refer to the tree of Figure 8.3.
a. Which node is the root?
b. What are the internal nodes?
c. How many descendants does node cs016/ have?
d. How many ancestors does node cs016/ have?
e. What are the siblings of node homeworks/?
f. Which nodes are in the subtree rooted at node projects/?
g. What is the depth of node papers/?
h. What is the height of the tree?
R-8.2 Show a tree achieving the worst-case running time for algorithm depth.
R-8.3 Give a justification of Proposition 8.4.
R-8.4 What is the running time of a call to T. height2(p) when called on a
position p distinct from the root of T? (See Code Fragment 8.5.)
R-8.5 Describe an algorithm, relying only on the BinaryTree operations, that
counts the number of leaves in a binary tree that are the left child of their
respective parent.
R-8.6 Let T be an n-node binary tree that may be improper. Describe how to
represent T by means of a proper binary tree T  with O(n) nodes.
R-8.7 What are the minimum and maximum number of internal and external
nodes in an improper binary tree with n nodes?
R-8.8 Answer the following questions so as to justify Proposition 8.8.
a. What is the minimum number of external nodes for a proper binary
tree with height h? Justify your answer.
b. What is the maximum number of external nodes for a proper binary
tree with height h? Justify your answer.
c. Let T be a proper binary tree with height h and n nodes. Show that

log(n + 1) − 1 ≤ h ≤ (n − 1)/2.

d. For which values of n and h can the above lower and upper bounds
on h be attained with equality?
R-8.9 Give a proof by induction of Proposition 8.9.
R-8.10 Give a direct implementation of the num children method within the class
BinaryTree.

www.it-ebooks.info
8.6. Exercises 353
R-8.11 Find the value of the arithmetic expression associated with each subtree
of the binary tree of Figure 8.8.
R-8.12 Draw an arithmetic expression tree that has four external nodes, storing
the numbers 1, 5, 6, and 7 (with each number stored in a distinct external
node, but not necessarily in this order), and has three internal nodes, each
storing an operator from the set {+, −, ×, /}, so that the value of the root
is 21. The operators may return and act on fractions, and an operator may
be used more than once.
R-8.13 Draw the binary tree representation of the following arithmetic expres-
sion: “(((5 + 2) ∗ (2 − 1))/((2 + 9) + ((7 − 2) − 1)) ∗ 8)”.
R-8.14 Justify Table 8.2, summarizing the running time of the methods of a tree
represented with a linked structure, by providing, for each method, a de-
scription of its implementation, and an analysis of its running time.
R-8.15 The LinkedBinaryTree class provides only nonpublic versions of the up-
date methods discussed on page 319. Implement a simple subclass named
MutableLinkedBinaryTree that provides public wrapper functions for each
of the inherited nonpublic update methods.
R-8.16 Let T be a binary tree with n nodes, and let f () be the level numbering
function of the positions of T , as given in Section 8.3.2.
a. Show that, for every position p of T , f (p) ≤ 2n − 2.
b. Show an example of a binary tree with seven nodes that attains the
above upper bound on f (p) for some position p.
R-8.17 Show how to use the Euler tour traversal to compute the level number
f (p), as defined in Section 8.3.2, of each position in a binary tree T .
R-8.18 Let T be a binary tree with n positions that is realized with an array rep-
resentation A, and let f () be the level numbering function of the positions
of T , as given in Section 8.3.2. Give pseudo-code descriptions of each of
the methods root, parent, left, right, is leaf, and is root.
R-8.19 Our definition of the level numbering function f (p), as given in Sec-
tion 8.3.2, began with the root having number 0. Some authors prefer
to use a level numbering g(p) in which the root is assigned number 1, be-
cause it simplifies the arithmetic for finding neighboring positions. Redo
Exercise R-8.18, but assuming that we use a level numbering g(p) in
which the root is assigned number 1.
R-8.20 Draw a binary tree T that simultaneously satisfies the following:
• Each internal node of T stores a single character.
• A preorder traversal of T yields EXAMFUN.
• An inorder traversal of T yields MAFXUEN.
R-8.21 In what order are positions visited during a preorder traversal of the tree
of Figure 8.8?

www.it-ebooks.info
354 Chapter 8. Trees
R-8.22 In what order are positions visited during a postorder traversal of the tree
of Figure 8.8?
R-8.23 Let T be an ordered tree with more than one node. Is it possible that the
preorder traversal of T visits the nodes in the same order as the postorder
traversal of T ? If so, give an example; otherwise, explain why this cannot
occur. Likewise, is it possible that the preorder traversal of T visits the
nodes in the reverse order of the postorder traversal of T ? If so, give an
example; otherwise, explain why this cannot occur.
R-8.24 Answer the previous question for the case when T is a proper binary tree
with more than one node.
R-8.25 Consider the example of a breadth-first traversal given in Figure 8.17.
Using the annotated numbers from that figure, describe the contents of
the queue before each pass of the while loop in Code Fragment 8.14. To
get started, the queue has contents {1} before the first pass, and contents
{2, 3, 4} before the second pass.
R-8.26 The collections.deque class supports an extend method that adds a col-
lection of elements to the end of the queue at once. Reimplement the
breadthfirst method of the Tree class to take advantage of this feature.
R-8.27 Give the output of the function parenthesize(T, T.root( )), as described
in Code Fragment 8.25, when T is the tree of Figure 8.8.
R-8.28 What is the running time of parenthesize(T, T.root( )), as given in Code
Fragment 8.25, for a tree T with n nodes?
R-8.29 Describe, in pseudo-code, an algorithm for computing the number of de-
scendants of each node of a binary tree. The algorithm should be based
on the Euler tour traversal.
R-8.30 The build expression tree method of the ExpressionTree class requires
input that is an iterable of string tokens. We used a convenient exam-
ple, (((3+1)x4)/((9-5)+2)) , in which each character is its own to-
ken, so that the string itself sufficed as input to build expression tree.
In general, a string, such as (35 + 14) , must be explicitly tokenized
into list [ ( , 35 , + , 14 , ) ] so as to ignore whitespace and to
recognize multidigit numbers as a single token. Write a utility method,
tokenize(raw), that returns such a list of tokens for a raw string.

Creativity
C-8.31 Define the internal path length, I(T ), of a tree T to be the sum of the
depths of all the internal positions in T . Likewise, define the external path
length, E(T ), of a tree T to be the sum of the depths of all the external
positions in T . Show that if T is a proper binary tree with n positions, then
E(T ) = I(T ) + n − 1.

www.it-ebooks.info
8.6. Exercises 355
C-8.32 Let T be a (not necessarily proper) binary tree with n nodes, and let D be
the sum of the depths of all the external nodes of T . Show that if T has the
minimum number of external nodes possible, then D is O(n) and if T has
the maximum number of external nodes possible, then D is O(n log n).
C-8.33 Let T be a (possibly improper) binary tree with n nodes, and let D be the
sum of the depths of all the external nodes of T . Describe a configuration
for T such that D is Ω(n2 ). Such a tree would be the worst case for the
asymptotic running time of method height1 (Code Fragment 8.4).
C-8.34 For a tree T , let nI denote the number of its internal nodes, and let nE
denote the number of its external nodes. Show that if every internal node
in T has exactly 3 children, then nE = 2nI + 1.
C-8.35 Two ordered trees T  and T  are said to be isomorphic if one of the fol-
lowing holds:
• Both T  and T  are empty.
• The roots of T  and T  have the same number k ≥ 0 of subtrees, and
the ith such subtree of T  is isomorphic to the ith such subtree of T 
for i = 1, . . . , k.
Design an algorithm that tests whether two given ordered trees are iso-
morphic. What is the running time of your algorithm?
C-8.36 Show that there are more than 2n improper binary trees with n internal
nodes such that no pair are isomorphic (see Exercise C-8.35).
C-8.37 If we exclude isomorphic trees (see Exercise C-8.35), exactly how many
proper binary trees exist with exactly 4 leaves?
C-8.38 Add support in LinkedBinaryTree for a method, delete subtree(p), that
removes the entire subtree rooted at position p, making sure to maintain
the count on the size of the tree. What is the running time of your imple-
mentation?
C-8.39 Add support in LinkedBinaryTree for a method, swap(p,q), that has the
effect of restructuring the tree so that the node referenced by p takes the
place of the node referenced by q, and vice versa. Make sure to properly
handle the case when the nodes are adjacent.
C-8.40 We can simplify parts of our LinkedBinaryTree implementation if we
make use of of a single sentinel node, referenced as the sentinel member
of the tree instance, such that the sentinel is the parent of the real root of
the tree, and the root is referenced as the left child of the sentinel. Fur-
thermore, the sentinel will take the place of None as the value of the left
or right member for a node without such a child. Give a new imple-
mentation of the update methods delete and attach, assuming such a
representation.

www.it-ebooks.info
356 Chapter 8. Trees
C-8.41 Describe how to clone a LinkedBinaryTree instance representing a proper
binary tree, with use of the attach method.
C-8.42 Describe how to clone a LinkedBinaryTree instance representing a (not
necessarily proper) binary tree, with use of the add left and add right
methods.
C-8.43 We can define a binary tree representation T  for an ordered general tree
T as follows (see Figure 8.23):
• For each position p of T , there is an associated position p of T  .
• If p is a leaf of T , then p in T  does not have a left child; otherwise
the left child of p is q , where q is the first child of p in T .
• If p has a sibling q ordered immediately after it in T , then q is the
right child of p in T ; otherwise p does not have a right child.
Given such a representation T  of a general ordered tree T , answer each
of the following questions:
a. Is a preorder traversal of T  equivalent to a preorder traversal of T ?
b. Is a postorder traversal of T  equivalent to a postorder traversal of T ?
c. Is an inorder traversal of T  equivalent to one of the standard traver-
sals of T ? If so, which one?

A A

B C D B

E F G E C

F D

(a) (b)
Figure 8.23: Representation of a tree with a binary tree: (a) tree T ; (b) binary tree
T  for T . The dashed edges connect nodes of T  that are siblings in T .

C-8.44 Give an efficient algorithm that computes and prints, for every position p
of a tree T , the element of p followed by the height of p’s subtree.
C-8.45 Give an O(n)-time algorithm for computing the depths of all positions of
a tree T , where n is the number of nodes of T .
C-8.46 The path length of a tree T is the sum of the depths of all positions in T .
Describe a linear-time method for computing the path length of a tree T .
C-8.47 The balance factor of an internal position p of a proper binary tree is the
difference between the heights of the right and left subtrees of p. Show
how to specialize the Euler tour traversal of Section 8.4.6 to print the
balance factors of all the internal nodes of a proper binary tree.

www.it-ebooks.info
8.6. Exercises 357
C-8.48 Given a proper binary tree T , define the reflection of T to be the binary
tree T  such that each node v in T is also in T  , but the left child of v in T
is v’s right child in T  and the right child of v in T is v’s left child in T  .
Show that a preorder traversal of a proper binary tree T is the same as the
postorder traversal of T ’s reflection, but in reverse order.
C-8.49 Let the rank of a position p during a traversal be defined such that the first
element visited has rank 1, the second element visited has rank 2, and so
on. For each position p in a tree T , let pre(p) be the rank of p in a preorder
traversal of T , let post(p) be the rank of p in a postorder traversal of T , let
depth(p) be the depth of p, and let desc(p) be the number of descendants
of p, including p itself. Derive a formula defining post(p) in terms of
desc(p), depth(p), and pre(p), for each node p in T .
C-8.50 Design algorithms for the following operations for a binary tree T :
• preorder next(p): Return the position visited after p in a preorder
traversal of T (or None if p is the last node visited).
• inorder next(p): Return the position visited after p in an inorder
traversal of T (or None if p is the last node visited).
• postorder next(p): Return the position visited after p in a postorder
traversal of T (or None if p is the last node visited).
What are the worst-case running times of your algorithms?
C-8.51 To implement the preorder method of the LinkedBinaryTree class, we re-
lied on the convenience of Python’s generator syntax and the yield state-
ment. Give an alternative implementation of preorder that returns an ex-
plicit instance of a nested iterator class. (See Section 2.3.4 for discussion
of iterators.)
C-8.52 Algorithm preorder draw draws a binary tree T by assigning x- and y-
coordinates to each position p such that x(p) is the number of nodes pre-
ceding p in the preorder traversal of T and y(p) is the depth of p in T .
a. Show that the drawing of T produced by preorder draw has no pairs
of crossing edges.
b. Redraw the binary tree of Figure 8.22 using preorder draw.
C-8.53 Redo the previous problem for the algorithm postorder draw that is simi-
lar to preorder draw except that it assigns x(p) to be the number of nodes
preceding position p in the postorder traversal.
C-8.54 Design an algorithm for drawing general trees, using a style similar to the
inorder traversal approach for drawing binary trees.
C-8.55 Exercise P-4.27 described the walk function of the os module. This func-
tion performs a traversal of the implicit tree represented by the file system.
Read the formal documentation for the function, and in particular its use
of an optional Boolean parameter named topdown. Describe how its be-
havior relates to tree traversal algorithms described in this chapter.

www.it-ebooks.info
358 Chapter 8. Trees
Sales (
Sales Domestic
International (
Canada
S. America
Domestic International
Overseas (
Africa
Europe
Canada S. America Overseas Asia
Australia
)
)
Africa Europe Asia Australia )
(a) (b)
Figure 8.24: (a) Tree T ; (b) indented parenthetic representation of T .

C-8.56 The indented parenthetic representation of a tree T is a variation of the


parenthetic representation of T (see Code Fragment 8.25) that uses inden-
tation and line breaks as illustrated in Figure 8.24. Give an algorithm that
prints this representation of a tree.
C-8.57 Let T be a binary tree with n positions. Define a Roman position to be
a position p in T , such that the number of descendants in p’s left subtree
differ from the number of descendants in p’s right subtree by at most 5.
Describe a linear-time method for finding each position p of T , such that
p is not a Roman position, but all of p’s descendants are Roman.
C-8.58 Let T be a tree with n positions. Define the lowest common ancestor
(LCA) between two positions p and q as the lowest position in T that has
both p and q as descendants (where we allow a position to be a descendant
of itself ). Given two positions p and q, describe an efficient algorithm for
finding the LCA of p and q. What is the running time of your algorithm?
C-8.59 Let T be a binary tree with n positions, and, for any position p in T , let d p
denote the depth of p in T . The distance between two positions p and q
in T is d p + dq − 2da , where a is the lowest common ancestor (LCA) of p
and q. The diameter of T is the maximum distance between two positions
in T . Describe an efficient algorithm for finding the diameter of T . What
is the running time of your algorithm?
C-8.60 Suppose each position p of a binary tree T is labeled with its value f (p) in
a level numbering of T . Design a fast method for determining f (a) for the
lowest common ancestor (LCA), a, of two positions p and q in T , given
f (p) and f (q). You do not need to find position a, just value f (a).
C-8.61 Give an alternative implementation of the build expression tree method
of the ExpressionTree class that relies on recursion to perform an implicit
Euler tour of the tree that is being built.

www.it-ebooks.info
8.6. Exercises 359
C-8.62 Note that the build expression tree function of the ExpressionTree class
is written in such a way that a leaf token can be any string; for exam-
ple, it parses the expression (a*(b+c)) . However, within the evaluate
method, an error would occur when attempting to convert a leaf token to
a number. Modify the evaluate method to accept an optional Python dic-
tionary that can be used to map such string variables to numeric values,
with a syntax such as T.evaluate({ a :3, b :1, c :5}). In this way,
the same algebraic expression can be evaluated using different values.
C-8.63 As mentioned in Exercise C-6.22, postfix notation is an unambiguous way
of writing an arithmetic expression without parentheses. It is defined so
that if “(exp1 ) op (exp2 )” is a normal (infix) fully parenthesized expres-
sion with operation op, then its postfix equivalent is “pexp1 pexp2 op”,
where pexp1 is the postfix version of exp1 and pexp2 is the postfix ver-
sion of exp2 . The postfix version of a single number or variable is just
that number or variable. So, for example, the postfix version of the infix
expression “((5 + 2) ∗ (8 − 3))/4” is “5 2 + 8 3 − ∗ 4 /”. Implement a
postfix method of the ExpressionTree class of Section 8.5 that produces
the postfix notation for the given expression.

Projects
P-8.64 Implement the binary tree ADT using the array-based representation de-
scribed in Section 8.3.2.
P-8.65 Implement the tree ADT using a linked structure as described in Sec-
tion 8.3.3. Provide a reasonable set of update methods for your tree.
P-8.66 The memory usage for the LinkedBinaryTree class can be streamlined by
removing the parent reference from each node, and instead having each
Position instance keep a member, path, that is a list of nodes representing
the entire path from the root to that position. (This generally saves mem-
ory because there are typically relatively few stored position instances.)
Reimplement the LinkedBinaryTree class using this strategy.
P-8.67 A slicing floor plan divides a rectangle with horizontal and vertical sides
using horizontal and vertical cuts. (See Figure 8.25a.) A slicing floor plan
can be represented by a proper binary tree, called a slicing tree, whose
internal nodes represent the cuts, and whose external nodes represent the
basic rectangles into which the floor plan is decomposed by the cuts. (See
Figure 8.25b.) The compaction problem for a slicing floor plan is defined
as follows. Assume that each basic rectangle of a slicing floor plan is
assigned a minimum width w and a minimum height h. The compaction
problem is to find the smallest possible height and width for each rectangle
of the slicing floor plan that is compatible with the minimum dimensions

www.it-ebooks.info
360 Chapter 8. Trees

E F

A E F
C D
A B

B C D

(a) (b)
Figure 8.25: (a) Slicing floor plan; (b) slicing tree associated with the floor plan.

of the basic rectangles. Namely, this problem requires the assignment of


values h(p) and w(p) to each position p of the slicing tree such that:


⎪ w
if p is a leaf whose basic rectangle has




minimum width w





⎪ if p is an internal position, associated with

⎨ max(w(), w(r)) a horizontal cut, with left child  and right
w(p) =

⎪ child r



⎪ if p is an internal position, associated with



⎪ w() + w(r) a vertical cut, with left child  and right



⎩ child r


⎪ if p is a leaf node whose basic rectangle

⎪ h

⎪ has minimum height h







⎪ if p is an internal position, associated with
⎨ h() + h(r) a horizontal cut, with left child  and right
h(p) =

⎪ child r



⎪ if p is an internal position, associated with



⎪ max(h(), h(r)) a vertical cut, with left child  and right



⎩ child r

Design a data structure for slicing floor plans that supports the operations:
• Create a floor plan consisting of a single basic rectangle.
• Decompose a basic rectangle by means of a horizontal cut.
• Decompose a basic rectangle by means of a vertical cut.
• Assign minimum height and width to a basic rectangle.
• Draw the slicing tree associated with the floor plan.
• Compact and draw the floor plan.

www.it-ebooks.info
Chapter Notes 361
P-8.68 Write a program that can play Tic-Tac-Toe effectively. (See Section 5.6.)
To do this, you will need to create a game tree T , which is a tree where
each position corresponds to a game configuration, which, in this case,
is a representation of the Tic-Tac-Toe board. (See Section 8.4.2.) The
root corresponds to the initial configuration. For each internal position p
in T , the children of p correspond to the game states we can reach from
p’s game state in a single legal move for the appropriate player, A (the
first player) or B (the second player). Positions at even depths correspond
to moves for A and positions at odd depths correspond to moves for B.
Leaves are either final game states or are at a depth beyond which we do
not want to explore. We score each leaf with a value that indicates how
good this state is for player A. In large games, like chess, we have to use a
heuristic scoring function, but for small games, like Tic-Tac-Toe, we can
construct the entire game tree and score leaves as +1, 0, −1, indicating
whether player A has a win, draw, or lose in that configuration. A good
algorithm for choosing moves is minimax. In this algorithm, we assign a
score to each internal position p in T , such that if p represents A’s turn, we
compute p’s score as the maximum of the scores of p’s children (which
corresponds to A’s optimal play from p). If an internal node p represents
B’s turn, then we compute p’s score as the minimum of the scores of p’s
children (which corresponds to B’s optimal play from p).
P-8.69 Implement the tree ADT using the binary tree representation described in
Exercise C-8.43. You may adapt the LinkedBinaryTree implementation.
P-8.70 Write a program that takes as input a general tree T and a position p of T
and converts T to another tree with the same set of position adjacencies,
but now with p as its root.

Chapter Notes
Discussions of the classic preorder, inorder, and postorder tree traversal methods can be
found in Knuth’s Fundamental Algorithms book [64]. The Euler tour traversal technique
comes from the parallel algorithms community; it is introduced by Tarjan and Vishkin [93]
and is discussed by JáJá [54] and by Karp and Ramachandran [58]. The algorithm for
drawing a tree is generally considered to be a part of the “folklore” of graph-drawing al-
gorithms. The reader interested in graph drawing is referred to the book by Di Battista,
Eades, Tamassia, and Tollis [34] and the survey by Tamassia and Liotta [92]. The puzzle
in Exercise R-8.12 was communicated by Micha Sharir.

www.it-ebooks.info

You might also like