Extracted Pages From Data Structures and Algorithms in Python (300-361)
Extracted Pages From Data Structures and Algorithms in Python (300-361)
Trees
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
www.it-ebooks.info
8.1. General Trees 301
Electronics R’Us
Figure 8.2: A tree with 17 nodes representing the organization of a fictitious cor-
poration. The root stores Electronics R’Us. The children of the root store R&D,
Sales, Purchasing, and Manufacturing. The internal nodes store Sales, Interna-
tional, Overseas, Electronics R’Us, and Manufacturing.
www.it-ebooks.info
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/
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
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
§ 1.1 ... § 1.4 § 5.1 ... § 5.7 § 6.1 ... § 6.5 § 9.1 ... § 9.6
Let’s look back at the other examples of trees that we have described thus far,
and consider whether the order of children is significant. A family tree that de-
scribes generational relationships, as in Figure 8.1, is often modeled as an ordered
tree, with siblings ordered according to their birth.
In contrast, an organizational chart for a company, as in Figure 8.2, is typically
considered an unordered tree. Likewise, when using a tree to describe an inher-
itance hierarchy, as in Figure 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
The tree ADT then supports the following accessor methods, allowing a user to
navigate the various positions of a tree:
T.is leaf(p): Return True if position p does not have any children.
T.is empty( ): Return True if tree T does not contain any positions.
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
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.
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.
www.it-ebooks.info
8.2. Binary Trees 311
Yes No
Yes No
Yes No
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.
Incidentally, we can also define a binary tree in a recursive way such that a binary
tree is either empty or consists of:
www.it-ebooks.info
8.2. Binary Trees 313
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
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
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
∅ ∅
parent
∅ ∅ ∅ ∅
(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.
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
• 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: 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
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
www.it-ebooks.info
8.3. Implementing Trees 327
New York
parent
element
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: 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
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
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.
Code Fragment 8.13: Algorithm postorder for performing the postorder traversal of
a subtree rooted at position p of a tree T.
Paper
Running-Time Analysis
Both preorder and postorder traversal algorithms are efficient ways to access all the
positions of a tree. The analysis of either of these traversal algorithms is similar to
that of algorithm 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
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.
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
Algorithm inorder(p):
if p has a left child lc then
inorder(lc) {recursively traverse the left subtree of p}
perform the “visit” action for position p
if p has a right child rc then
inorder(rc) {recursively traverse the right subtree of p}
/ +
× + × 6
+ 3 − 2 3 −
3 1 9 5 7 4
The inorder traversal algorithm has several important applications. When using
a binary tree to represent an arithmetic expression, as in Figure 8.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
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.
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).
www.it-ebooks.info
8.4. Tree Traversal Algorithms 337
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.
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.
www.it-ebooks.info
8.4. Tree Traversal Algorithms 341
/ +
× + × 6
+ 3 − 2 3 −
3 1 9 5 7 4
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.
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:
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.
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
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.
www.it-ebooks.info
348 Chapter 8. Trees
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.
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.
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 .
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.
⎧
⎪ 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