Data Structures · Lecture Notes

Chapter 9: Trees

Arrays, linked lists, stacks, and queues keep their elements in a line: each has at most one predecessor and one successor. Much real data is hierarchical instead — folders inside folders, chapters inside a book, elements nested in a web page — and the structure that holds it naturally is the tree. We start with its vocabulary (root, parent, child, leaf, degree, depth, height), then turn to binary trees, their full, complete, and perfect varieties, and how node count bounds height. After storing trees with arrays and with links, we reach the heart of the chapter: preorder, inorder, postorder, and level-order traversals, coded recursively and then with an explicit stack and queue, each traced step by step. The last sections cover expression trees, recursive tree methods, and rebuilding a tree from two traversals.

Learning outcomes

Core concepts
Explain root, parent, child, sibling, ancestor, descendant, leaf, degree, depth, and height, and find these values in a given tree.
Representations
Represent trees with linked nodes and with arrays; compare the memory and access costs of the two.
Traversals
Carry out preorder, inorder, postorder, and level-order traversals by hand and in code; trace recursive calls on the call stack.
Applications and cost
Compare general and binary trees by their uses and operation costs; write recursive methods that work on trees.

9.1The tree concept

Every structure so far has been linear: each element is followed by at most one other. An organization chart, a file system, or a family tree will not fit on a line, because an element can have several elements below it, each with its own below it. This section sets up the language of trees, the structure for such hierarchies.

9.1.1What is a tree?

A tree is a nonlinear structure that stores elements hierarchically. Each element is a node, and a line joining two nodes is an edge. The single node at the top is the root; computer scientists draw trees upside down, as in Figure 9.1.

rootleavesedge
Figure 9.1: A tree with seven nodes. The root at the top branches into two children, which branch into their own. The four bottom nodes have no children: they are leaves. Six edges join the seven nodes.

Each edge joins an upper node, the parent, to a lower one, its child. One rule makes a tree a tree: every node except the root has exactly one parent, and the root has none. The number of children is unlimited — zero, one, or many.

Definition 9.1

A tree is a structure of nodes and of edges joining them in parent–child relationships. A nonempty tree satisfies two conditions: (1) exactly one node, the root, has no parent; (2) every other node has exactly one parent, and from every node we reach the root by climbing from parent to parent.

Recursively: a tree is either empty or a root with zero or more trees hanging below it, the root's subtrees.

The second condition rules out a cycle: you cannot leave a node, walk along edges without reusing one, and return. A cycle would force some node on it either to have two parents or to climb forever without reaching the root.

The most used numerical fact about trees concerns edges. Every non-root node is joined to its parent by exactly one edge, so pairing each edge with its lower end is a one-to-one correspondence.

Theorem 9.1 (Number of edges)

Every tree with n ≥ 1 nodes has exactly n − 1 edges.

Pair each edge with its lower end, the child. A node has one parent, so each child gets at most one edge; the root is the lower end of no edge, and every other node of exactly one. The edges thus match the n − 1 non-root nodes. □

Figure 9.1 agrees: seven nodes, six edges. The rule works both ways: the node count gives the edge count at once, and a connected structure with at least as many edges as nodes must contain a cycle.

9.1.2Hierarchies in everyday life

People drew hierarchies as trees long before computers.

  • File system. The root directory (/ on Unix-like systems) is at the top. Folders hold folders and files; files hold nothing. Folders are internal nodes, files leaves.
  • Book. Parts divide into chapters, chapters into sections; the table of contents is the tree written with indentation.
  • Geography. Continents contain countries, countries provinces, provinces cities.
  • Web page. <html> contains <head> and <body>, which contain further elements; the browser builds a tree of them, the Document Object Model (DOM).
/etcbinhomevarhostsnetworkannalicenotes.txthwphoto.jpg
Figure 9.2: A file system tree. The root directory / holds etc, bin, home, and var; home holds the folders ann and alice. The orange boxes are files, the leaves of the tree.

Figure 9.2 also shows why a file has one full path: notes.txt is reached only along /home/ann/notes.txt. With one parent per node, the path down from the root is unique. Shortcut (link) files add extra connections, and the structure stops being a pure tree.

9.1.3When is a structure a tree?

Without a marked root there is no notion of parent, so for an arbitrary linked structure we restate the definition: the structure must be connected (a path joins every pair of nodes) and have no cycle. Then it has one edge fewer than nodes, and any node can be chosen as the root; each node's parent is then its unique neighbor toward the root.

AHGFBCEDJIKPLMNO IDJKMEPNOHLAGBFC
Figure 9.3: Left: a linked structure with 16 nodes, A to P, and no root marked. Right: the same structure redrawn with I as the root. I's children are D, J, K, and M; A, B, and C hang below D–E–H.
Example 9.1

Is the structure on the left of Figure 9.3 a tree?

  1. Nodes and edges. 16 nodes, A to P, and 15 edges: A–B, B–C, A–H, H–G, G–F, H–E, E–D, D–I, I–J, I–K, K–P, P–L, I–M, M–N, M–O.
  2. Connectivity. From I we reach D, J, K, M; from D, E; from E, H; from H, A and G; from A, B; from B, C; from G, F; from K, P; from P, L; from M, N and O — all 16 nodes.
  3. Conclusion. Connected, with 15 edges for 16 nodes: no cycle, so a tree. Any node, I for instance, can be the root, as drawn on the right.

We never checked for cycles; the count did it. Connecting n nodes takes at least n − 1 edges, and one more edge joins two nodes that already have a path between them, closing a cycle. So “connected with n − 1 edges” means “connected and acyclic.”

cycletwo parentsdisconnected
Figure 9.4: Three non-trees. Left: three nodes form a cycle (the red edge closes it). Middle: the bottom node has two parents. Right: two disconnected pieces.

Figure 9.4 shows the three typical violations. The triangle has three edges for three nodes, one too many, so a cycle forms. In the middle, whatever the root, the bottom node is reached by two paths. On the right, three nodes share one edge; with an edge missing, the structure splits. A collection of disjoint trees like this is a forest.

Common mistake

Calling a structure a tree because it is connected. A connected structure with 10 nodes and 10 edges is not a tree: a tree has 9, and the extra edge closes a cycle. Nor are n − 1 edges enough alone: a piece with a cycle plus an isolated node can have them too. Check both connectivity and n − 1 edges (or connectivity and no cycle).

9.1.4Root, internal nodes, and leaves

Whether a node has children matters in nearly every tree algorithm: a recursive method usually stops at a leaf and descends into the children of an internal node.

  • Root: the top node, the one without a parent — the root directory of a file system.
  • Internal node: a node with at least one child — a folder.
  • Leaf: a node with no children, also called an external node — a file.
12934567rootinternal nodeleaves: 9, 4, 5, 6, 7
Figure 9.5: A tree rooted at 1. Nodes 2 and 3 are internal; 9, 4, 5, 6, and 7 have no children and are leaves. The root has children, so it is internal too.

Figure 9.5 makes two subtle points. First, “root” and “internal node” overlap: the root is defined by position, internal nodes and leaves by their number of children. Second, leaves need not share a level: 9 is a child of the root and a leaf, like 4, 5, 6, and 7. In a one-node tree the root is a leaf.

Common mistake

Looking for leaves only in the bottom row. A leaf is a node without children, wherever it is drawn: node 9 in Figure 9.5 sits in the second row and is a leaf. Nor should the root always be ruled out: in a one-node tree it is a leaf.

9.1.5Relationships between nodes

The terminology comes from family trees. A node's parent is directly above it, its children directly below, and children of one parent are siblings. The nodes on the path up to the root are its ancestors; all nodes reachable going down are its descendants. Table 9.1 illustrates these on Figure 9.6.

ABCDEFGHIJKLM
Figure 9.6: An example tree. A is the root, with children B, C, and D; D's children H, I, J are highlighted. M is below H, E and F below B, K and L below E, G below C.
Table 9.1: Relationships in a tree, with examples from Figure 9.6.
TermMeaningExample
ParentThe node directly aboveD is H's parent.
ChildA node directly belowH, I, and J are D's children.
SiblingA node with the same parentH, I, and J are siblings.
AncestorA node on the path to the rootM's ancestors are H, D, and A.
DescendantA node in the subtreeD's descendants are H, I, J, and M.

Ancestor and descendant are inverses: M's ancestors are H, D, A, so M is a descendant of each. The root is everyone's ancestor, a leaf no one's. Some sources count a node as its own ancestor and descendant; here they exclude the node, so a node's descendants are the other nodes of its subtree.

Sharing a row does not make nodes siblings. In Figure 9.6, F's parent is B and G's is C, so they are cousins, not siblings; siblinghood means a shared parent.

Common mistake

Treating nodes on one level as siblings, or a node's descendants as just its children. D's descendants are the whole subtree: H, I, J, and also H's child M. Likewise M's ancestors are the whole path to the root, H, D, A, not just its parent.

Figure 9.7 shows the tree in four frames of the slide drawing that introduces the terms. Of its 13 nodes, A through M, seven are leaves (K, L, F, G, M, I, J) and six internal (A, B, C, D, E, H); 7 + 6 = 13, and by Theorem 9.1 there are 12 edges.

d=0d=1d=2d=3ABCDEFGHIJKLM d=0d=1d=2d=3ABCDEFGHIJKLM321321
d=0d=1d=2d=3ABCDEFGHIJKLM d=0d=1d=2d=3ABCDEFGHIJKLM
Figure 9.7: Terms on the 13-node tree (left to right, top to bottom). (a) M's ancestors: the path M–H–D–A. (b) Degrees: A 3, B 2, C 1, D 3, E 2, H 1; the tree has degree 3. (c) Height: the path A–B–E–K to the farthest leaf, 3 edges. (d) D's subtree: D, H, I, J, M. Dashed lines mark depths d = 0, 1, 2, 3.

9.1.6Degree, depth, level, and height

Nearly every cost analysis on trees rests on a few measures of how much a node branches and how far it is from the root.

Definition 9.2
  • The degree of a node is its number of children (subtrees); a tree's degree is the largest degree of its nodes.
  • The depth of a node is the number of edges on the path from the root to it; the root has depth 0.
  • The nodes at depth d form level d.
  • The height of a node is the number of edges on the path from it down to its farthest leaf; a leaf has height 0.
  • The height of a tree is the height of its root, which equals the largest depth.

Depth looks up: how far below the root is this node? Height looks down: how long is the longest branch beneath it? The root has depth 0 and the tree's height; a leaf has height 0 and a depth that depends on where it sits.

Table 9.2: Tree measures and their values in the 13-node tree of Figure 9.7.
MeasureDefinitionExample
DegreeNumber of children of the nodeD → 3
DepthEdges from the root to the node; 0 at the rootE → 2 (A→B→E)
LevelThe nodes at the same depthLevel 2: E, F, G, H, I, J
Node heightEdges from the node to the farthest leaf; 0 at a leafD → 2 (D→H→M)
Tree heightHeight of the root = largest depth3

These notes count edges. Some books count nodes and number levels from 1, so every depth and height comes out one larger. Both are consistent, but mixing them shifts results by one; check a source's convention before comparing.

ABCDEFGHIJK
Figure 9.8: A tree for depth and height. A's children are B, C, D, E; B's are F and G; D's is H; E's are I and J; J's is K. The highlighted path A→E→J→K is the longest.
Example 9.2

Find the depth and height of every node in Figure 9.8.

  1. Depths, top down. A has depth 0; B, C, D, E depth 1; F, G, H, I, J depth 2; K depth 3.
  2. Heights, bottom up. Leaves C, F, G, H, I, K have height 0; an internal node's height is one more than its children's largest. J: 1 + 0 = 1. B: 1 + max(0, 0) = 1. D: 1. E, with children I (0) and J (1): 1 + max(0, 1) = 2.
  3. The root. A's children have heights 1, 0, 1, 2, so A's height is 1 + 2 = 3 — the tree's height, equal to the depth of the deepest node, K.
Table 9.3: Height and depth in the tree of Figure 9.8.
NodeABCDEFGHIJK
Height31012000010
Depth01111222223

Depth depends only on a node's ancestors, height on all its descendants. The code mirrors this: depth climbs the parent chain, height descends through the children.

Common mistake

Computing a node's height as the tree's height minus its depth. That works only on the longest branch. In Figure 9.8, B has depth 1 and the tree height 3, yet B's height is 1, not 3 − 1 = 2: the longest branch below B is one edge. Height always comes from the node's own subtree.

9.1.7Paths and subtrees

Walking from node to node along edges traces a path, formally a sequence alternating nodes and edges:

p = (v0, e1, v1, e2, …, ek, vk)

Each edge ei joins vi−1 and vi; the path has start(p) = v0, end(p) = vk, and length length(p) = k, its number of edges. A path repeating no node is a simple path. Depth and height are lengths of paths from the root down to a node and from a node down to its farthest leaf.

Theorem 9.2 (Unique path)

In a tree, there is exactly one simple path between any two nodes.

Existence: climb the parent chains of both nodes toward the root, and let c be the first node common to both. Climbing from the first node to c and descending to the second gives a simple path. Uniqueness: two different simple paths would split somewhere and later rejoin, and going out along one and back along the other would form a cycle, which a tree does not have. □

ABCDEFGHIJKLM
Figure 9.9: The unique path K → E → B → A → D → H → M. It climbs to A, the first common ancestor, then descends to M: 6 edges.

Figure 9.9 makes the proof concrete. K's ancestors are E, B, A and M's are H, D, A; the chains first meet at A, so the path climbs three edges and descends three, length 6, and no other path exists. “A file has exactly one full path” is a special case.

A subtree is a node together with all its descendants, a tree in its own right. The dashed frame in panel (d) of Figure 9.7 marks D's subtree: D, H, I, J, M. A folder with everything under it is a subtree, so copying or deleting a folder copies or deletes a subtree.

Subtrees make tree algorithms naturally recursive. A node's children are the roots of its subtrees, so a job on a tree can usually be phrased as: do it on each subtree, combine the results, and account for the root. Height is the first example: one more than the largest subtree height.

9.1.8Basic kinds of trees

The definition puts no limit on children, but limiting or ordering them often helps. Table 9.4 lists the four kinds we meet in this course.

Table 9.4: Basic kinds of trees.
KindProperty
General treeNo limit on the number of children.
k-ary treeEvery node has at most k children.
Binary treeEvery node has at most two children: left and right.
Binary search tree (BST)A binary tree in which, at every node, keys in the left subtree are smaller than the node's key and keys in the right subtree are larger.
general tree3-ary tree (k = 3)binary tree83101614binary search tree
Figure 9.10: Four kinds (left to right): a general tree, a tree with at most three children (k = 3), a binary tree, and a binary search tree with root 8, left child 3 (children 1 and 6), and right child 10 (right child 14).

The kinds nest: every BST is a binary tree, every binary tree a k-ary tree with k = 2, every k-ary tree a general tree. More constraints mean less flexibility but easier work. A BST search descends into one child per node, left for a smaller key, right for a larger: finding 6 on the right of Figure 9.10 goes left from 8 and right from 3, two comparisons.

These notes concentrate on binary trees. Every general tree can be stored as a binary tree, as the section on representations shows. Insertion, deletion, and balancing in BSTs are a large topic of their own, mentioned here only by definition.

9.1.9The tree ADT

Before using a tree in code, we must decide which operations give access to it. As with stacks and queues, it pays to separate the operations from their implementation; the description of what a structure does is its abstract data type (ADT). The Java interface below is the tree ADT of Goodrich, Tamassia, and Goldwasser.

public interface Tree<E> extends Iterable<E> {
  Position<E> root();
  Position<E> parent(Position<E> p);
  Iterable<Position<E>> children(Position<E> p);
  int numChildren(Position<E> p);
  boolean isInternal(Position<E> p);  // has a child
  boolean isExternal(Position<E> p);  // leaf
  boolean isRoot(Position<E> p);      // no parent
  int size();
  boolean isEmpty();
  Iterable<Position<E>> positions(); // all positions
}
// Position<E>: E getElement();

The interface is built around positions. A Position<E> stands for a place in the tree and has one operation, getElement(), returning the element there. The user never sees, references, or modifies the node objects; all access goes through ADT methods on positions. Whether the tree is linked or stored in an array is hidden, so client code survives a change of implementation.

The methods fall into three groups.

  • Navigation. root() returns the root's position (null if empty); parent(p) returns p's parent, or null at the root; children(p) yields the children in order, as an Iterable usable in a for-each loop; numChildren(p) is p's degree.
  • Queries. isInternal(p), isExternal(p), and isRoot(p) report whether a position is internal, a leaf, or the root. The names follow the reference textbook.
  • General methods. size() and isEmpty() report the node count and emptiness; positions() iterates over all positions and iterator() (from Iterable<E>) over all elements.

The binary tree ADT adds three methods: left(p) and right(p) return the children (or null), and sibling(p) the sibling.

ABCDEFGparent(p)pchildren(p): D, Eleafroot
Figure 9.11: The ADT methods on an example. p is the position of node B. parent(p) gives the root A, children(p) gives D and E. D is a leaf and A the root; E's only child is G, C's only child F.

On Figure 9.11: numChildren(p) returns 2; isInternal(p) is true, isExternal(p) and isRoot(p) false; root() gives A, parent(root()) is null, and size() is 7.

9.1.10Code trace: depth and height

With the ADT we can compute depth and height without knowing the implementation; both methods transcribe their definitions almost word for word.

int depth(Position<E> p) {
    if (isRoot(p)) return 0;
    return 1 + depth(parent(p));
}
int height(Position<E> p) {
    int h = 0;
    for (Position<E> c : children(p))
        h = Math.max(h, 1 + height(c));
    return h;
}

depth(p) follows the definition: 0 at the root, otherwise one more than the parent's depth. It climbs the parent chain one edge per call, so for depth d it is called d + 1 times: O(d + 1) time and at most d + 1 stack frames.

height(p) looks downward. Starting from h = 0, the loop raises h to 1 + height(c) for each child c; at a leaf the loop never runs and 0 is returned. The method is called once per node of p's subtree, and iterating over children costs their number, which sums to the edge count, so a subtree of np nodes takes O(np).

Let us trace depth(G) and then height(B) on Figure 9.11. Table 9.5 gives the call stack (bottom to top) and returned values.

Table 9.5: Trace of depth(G) and height(B). The call stack is written bottom to top.
StepRunning callCall stackWhat happens
1depth(G)main, depth(G)G is not the root; depth(E) is called for its parent E.
2depth(E)…, depth(E)E is not the root; depth(B) is called.
3depth(B)…, depth(B)B is not the root; depth(A) is called.
4depth(A)main, G, E, B, A (5 frames)A is the root: base case, returns 0.
5depth(B)main, G, E, BReturns 1 + 0 = 1.
6depth(E)main, G, EReturns 1 + 1 = 2.
7depth(G)main, GReturns 1 + 2 = 3. G has depth 3 (A–B–E–G).
8height(B)main, height(B)h = 0; children D, E. Recurse on the first child, D.
9height(D)…, B, DD has no children; the loop does not run, returns 0.
10height(B)main, Bh = max(0, 1 + 0) = 1; next child E.
11height(E)…, B, Eh = 0; recurse on the only child, G.
12height(G)…, B, E, GG is a leaf: returns 0.
13height(E)…, B, Eh = max(0, 1 + 0) = 1; no children left, returns 1.
14height(B)main, Bh = max(1, 1 + 1) = 2; no children left, returns 2.
ABCDEFG↑ 0running nowwaiting on stackhas returnednot called yet ABCDEFG↑ 0↑ 1↑ 2↑ 3running nowwaiting on stackhas returnednot called yet
ABCDEFG↑ 0running nowwaiting on stackhas returnednot called yet ABCDEFG↑ 0↑ 0↑ 1↑ 2running nowwaiting on stackhas returnednot called yet
Figure 9.12: Four moments of the trace. (a) depth(A) is at the base case; frames for G, E, B wait. (b) Values come back: B 1, E 2, G 3. (c) height(B) got 0 from D and entered E's subtree. (d) G returned 0, E 1; height(B) = 2.

The trace shows a symmetry. depth calls on the way up and computes on the way down: the root says 0, and each frame adds 1. height calls on the way down and computes on the way up: leaves say 0, and each internal node adds 1 to its children's maximum. height(B) was called four times, for exactly B's subtree (B, D, E, G); A, C, F were never touched.

A tree's height can be found with height(root()) in O(n), or as the largest depth, which climbs separately from every node at cost O(n + Σ dp). In a chain the depths 0, 1, …, n − 1 sum to n(n − 1)/2, so that costs O(n2). Use the first method.

Common mistake

Starting h at 1 in height, or returning 1 at a leaf: height is then measured in nodes and every value is one too large. With the edge-based definition, a leaf returns 0. Likewise, calling parent(p) in depth without the isRoot(p) check carries on at the root with a null position and fails.

9.2Binary trees

In a general tree a node may have any number of children, so each node must keep a variable-length child list. Limiting nodes to two children simplifies both storage and algorithms, which is why binary trees are the most widely used kind; expression trees, binary search trees, heaps, and Huffman trees are all binary.

9.2.1Definition of a binary tree

In a binary tree every node has at most two children, but what really sets it apart is that the children are named: each is a left child or a right child. Even an only child is on one particular side, and the side matters.

Definition 9.3

A binary tree is either empty or a root together with two binary trees, its left subtree and right subtree. The root of a nonempty left subtree is the main root's left child; likewise on the right.

The definition is recursive but not circular: the subtrees shrink at every step until we reach the empty tree, the base case. It also dictates how binary tree methods are written: return a value for the empty tree; otherwise apply the method to both subtrees and combine the results.

ABAB≠
Figure 9.13: Two different binary trees: B is A's left child on the left, its right child on the right. As general trees they are the same.

In both drawings of Figure 9.13, A has one child, B. As general trees they coincide; as binary trees one has an empty right subtree and the other an empty left one. The difference matters: in a BST left means smaller keys, and inorder traversal outputs B A for the left tree but A B for the right.

ABCDABCDEFGHIJK
Figure 9.14: Two binary trees: a left-skewed chain A, B, C, D, and an 11-node tree with evenly spread branches.

Nodes need not be spread evenly. In the left structure of Figure 9.14 every node has only a left child, and the tree has degenerated into a chain, a skewed tree. It is a valid binary tree but loses most of the advantages: four nodes in a chain have height 3, evenly arranged only 2. With a million nodes, the gap becomes about 20 versus a million.

9.2.2Full, complete, and perfect binary trees

Three names describe the shape of a binary tree. They sound alike, they are often confused, and not every source uses them the same way, so hold on to the precise definitions below.

Definition 9.4
  • Full binary tree: every node has 0 or 2 children.
  • Complete binary tree: every level except possibly the last is filled, and the last level is packed from the left with no gaps.
  • Perfect binary tree: every internal node has two children and all leaves are at the same depth.
Figure 9.15: Left to right: a full binary tree (0 or 2 children everywhere), a complete binary tree (upper levels filled, the last filling from the left), and a perfect binary tree (all leaves at one depth).

The definitions look at different things. Full is a local condition on each node and ignores overall shape, so a full tree can be very lopsided: if the right child is always a leaf and the left one branches again, the tree is full yet nearly a chain. Complete is a condition on levels; it allows one node with a single child, in the right position just above the last level. Perfect is the strongest: the tree has every node its height allows.

To test completeness, number the nodes level by level, left to right: root 1, its children 2 and 3, then 4 to 7. A tree with n nodes is complete exactly when they fill 1 to n without gaps; if 10 is empty while 11 is used, it is not. This numbering also underlies the array representation.

A perfect tree is both full and complete. Full and complete are otherwise independent: trees exist that are full but not complete, complete but not full, and neither.

Common mistake

Treating “full” and “complete” as the same, or assuming one implies the other. Full means 0 or 2 children everywhere; complete means all levels but the last are filled and the last fills from the left. A complete tree may have a node with one child, and a full tree may have gaps on upper levels. Check the two separately.

9.2.3Telling them apart on four trees

We apply the three definitions to the four trees of Figure 9.16.

ABCDEHI 12345678910
12345678911 1234567
Figure 9.16: Four binary trees. (a) Root A; B's children D and E; E's children H and I; C a leaf. (b) Numbered 1 to 10, with 8, 9, 10 on the last level from the left. (c) Nodes 1 to 9 and 11; position 10 empty. (d) A perfect tree numbered 1 to 7.
Example 9.3

Is each tree in Figure 9.16 full, complete, perfect?

  1. (a) A, B, E have 2 children and C, D, H, I none, so the tree is full. Level 2 holds only D and E — the positions under C are empty — yet H and I sit on level 3, so it is not complete. Its leaves lie at depths 1, 2, and 3, so it is not perfect.
  2. (b) Numbered 1 to 10 without gaps, levels 0–2 filled and 8, 9, 10 packed left: complete. But 5 has one child (10), so it is not full, hence not perfect.
  3. (c) Node 5 has only a right child, 11: not full. Position 10 is empty while 11 is used: not complete. So not perfect either.
  4. (d) Internal nodes 1, 2, 3 have two children and leaves 4–7 share a depth: perfect, hence full and complete.
Table 9.6: Classifying the four trees of Figure 9.16.
TreeFullCompletePerfect
(a)yesnono
(b)noyesno
(c)nonono
(d)yesyesyes

Rows (a) and (b) prove that full and complete are independent. Compare (b) and (c): in both, 5 has one child, but on the left (10) the numbering continues without gaps, while on the right (11) it leaves 10 empty. One node on the wrong side makes the tree incomplete.

9.2.4Node count and height

With at most two children per node, each level holds at most twice as many nodes as the one above. Every relation between node count and height follows from this; Table 9.7 collects them, and the justifications follow.

Table 9.7: Properties of binary trees.
PropertyFormulaExample
Max nodes at depth d2dd = 3 → at most 8 nodes
Max nodes in a tree of height h2h+1 − 1h = 3 → 15 (perfect tree)
Min nodes in a tree of height hh + 1skewed tree (chain)
Height of a tree with n nodes⌊log2 n⌋ ≤ h ≤ n − 1n = 15 → 3 ≤ h ≤ 14
Edges in a tree with n nodesn − 1each node has one parent
Leaves in a full binary treeinternal nodes + 17 nodes → 3 internal, 4 leaves
Theorem 9.3 (Bounds on node count)

In a binary tree, (a) there are at most 2d nodes at depth d; (b) a tree of height h has n nodes with h + 1 ≤ n ≤ 2h+1 − 1; (c) a tree with n ≥ 1 nodes has height in the range ⌊log2 n⌋ ≤ h ≤ n − 1.

(a) By induction: depth 0 holds only the root, 20 = 1; if depth d holds at most 2d nodes, each with at most two children, depth d + 1 holds at most 2d+1.

(b) Upper bound: depths run from 0 to h, so by (a) there are at most 1 + 2 + … + 2h = 2h+1 − 1 nodes. Lower bound: a path of h edges from the root to the deepest leaf contains h + 1 nodes.

(c) Solving (b) for h: h + 1 ≤ n gives h ≤ n − 1, and n ≤ 2h+1 − 1 gives log2 n < h + 1; since h is an integer, h ≥ ⌊log2 n⌋. □

A sum of consecutive powers of two is one less than the next power: 1 + 2 + 4 + 8 = 16 − 1. Only perfect trees reach the upper bound (15 nodes at height 3, 31 at height 4); chains reach the lower one (4 nodes at height 3).

Example 9.4

What are the smallest and largest possible heights of a binary tree with 1,000 nodes?

  1. Largest. A chain: n − 1 = 999.
  2. Smallest. Height h allows at most 2h+1 − 1 nodes: 511 for h = 8, too few; 1,023 for h = 9, enough. So 9, as the formula says: ⌊log2 1000⌋ = ⌊9.97⌋ = 9.
  3. Interpretation. A root-to-leaf walk takes about 10 steps in a balanced tree and about 1,000 in a chain.

That is why these relations matter. Searching for a key or inserting a node follows one path from the root toward a leaf, costing O(h). A balanced tree has h ≈ log2 n, about 20 levels for a million nodes (220 ≈ 106); a skewed tree has h = n − 1, as slow as a linked list. Balancing search trees closes this gap.

Common mistake

Writing the maximum node count for height h as 2h or 2h − 1. 2h bounds only the bottom level; summing all levels gives 2h+1 − 1. 2h − 1 is the formula for height measured in nodes and creeps in when conventions are mixed.

9.2.5Leaves and internal nodes in a full binary tree

A full binary tree always has exactly one more leaf than internal nodes; the 7-node perfect tree in Table 9.7 has 3 internal nodes and 4 leaves.

Theorem 9.4 (Leaves in a full binary tree)

A nonempty full binary tree with i internal nodes has l = i + 1 leaves. Hence n = i + l = 2i + 1 is always odd.

To see why, note that every full binary tree grows from a single node by one repeated operation: since a leaf cannot get a single child, it gets two at once. Figure 9.17 shows the growth.

Linternal i = 0leaves l = 1l − i = 1
ILILLinternal i = 2leaves l = 3l − i = 1
LLIILLILIinternal i = 4leaves l = 5l − i = 1
Figure 9.17: A full binary tree growing (I: internal node, L: leaf, or external node; orange: leaves just added). Top: one node, i = 0, l = 1. Middle: after two expansions, i = 2, l = 3. Bottom: after four, i = 4, l = 5. Always l − i = 1.

By induction on the number of nodes. Base: a one-node tree has i = 0, l = 1. Step: take the deepest leaf. Its parent has two children because the tree is full, and the sibling is a leaf too, or it would have deeper children. Deleting both leaves leaves a smaller full tree with l' = i' + 1. Putting them back turns one leaf into an internal node (i = i' + 1) and gives l = l' − 1 + 2 = l' + 1, so l − i = l' − i' = 1. □

So no full binary tree has an even number of nodes, 6 for instance, and the node count fixes the rest: l = (n + 1)/2 and i = (n − 1)/2. A full tree with 15 nodes has 8 leaves and 7 internal nodes.

Applications use this directly. In an expression tree with binary operators, operators are internal nodes and numbers leaves, so an expression always has one more number than operators: (3 + 4) × (5 − 2) has 3 operators and 4 numbers. In Huffman coding every internal node joins two subtrees, so k characters give k leaves and k − 1 internal nodes.

Common mistake

Applying l = i + 1 to every binary tree. Nodes with one child add internal nodes but no leaves: a three-node chain has 2 internal nodes and 1 leaf. In general, if i2 nodes have two children, there are i2 + 1 leaves, regardless of one-child nodes.

Counting edges proves this. With i1 one-child and i2 two-child nodes, there are n − 1 = l + i1 + i2 − 1 edges; since every edge leads down to a child, there are also i1 + 2i2. Equating them gives l = i2 + 1; in a full tree i1 = 0 and we recover the theorem.

9.3Representations

On paper a tree is nodes and lines; in memory it is either an array of consecutive cells or objects pointing to one another. The choice of representation decides how fast we reach parents and children, how much memory we use, and how easily the tree grows.

9.3.1Array representation

The level-by-level numbering gives a natural way to put a binary tree in an array: the root goes in the first cell, its children in the next two, the next level in the next four. A node's children and parent are then found from its index by simple arithmetic.

Table 9.8: Index relations for the node at index i in the array representation.
1-based (root at a[1])0-based (root at a[0])
Left child2i2i + 1
Right child2i + 12i + 2
Parent⌊i / 2⌋⌊(i − 1) / 2⌋

The 1-based formulas are simpler, so many books leave cell 0 empty. Why they work: the first node at depth d gets index 2d, since the levels above hold 2d − 1 nodes. If j nodes precede a node on its level, their 2j children precede its children on the next, which puts the left child of index i at 2i and the right child beside it. Integer division by 2 maps both 2i and 2i + 1 back to i.

The 0-based formulas shift indices by one: 0-based i is 1-based i + 1, whose left child 2i + 2 is 0-based 2i + 1; the others follow likewise.

ABCDEFGHI1234567890A1B2C3D4E5F6G7H8I9
Figure 9.18: A nine-node binary tree, A to I, in a 1-based array: A at index 1, B at 2, …, I at 9; cell 0 unused. Red numbers are indices.

In Figure 9.18, E is at index 5. Its children would be at 10 and 11, beyond the 9 nodes, so E is a leaf; its parent is at ⌊5 / 2⌋ = 2, B. Nodes carry no references: parent and child are implicit in the position, and each access is O(1) arithmetic.

Arrays suit complete binary trees best: their nodes fill indices 1 to n exactly, wasting no cells, which is why binary heaps almost always live in arrays. In other trees empty positions become empty cells; we compute the cost below.

9.3.2Code trace: index arithmetic and the path to the root

The code turns the 1-based formulas into three methods and prints every ancestor of a node up to the root.

int left(int i)    { return 2 * i; }
int right(int i)   { return 2 * i + 1; }
int parent(int i)  { return i / 2; }
void pathToRoot(char[] a, int i) {  // from i up to the root
  while (i >= 1) {
    System.out.print(a[i] + " ");
    i = parent(i);
  }
}

In parent, i / 2 is Java integer division, which rounds down: exactly ⌊i / 2⌋. pathToRoot(a, i) prints a[i] and moves i to the parent while i ≥ 1. The root's “parent” is ⌊1 / 2⌋ = 0, which stops the loop; that is why an empty cell 0 is handy, meaning “no parent.”

Table 9.9: Index computations on the array of Figure 9.18 and a trace of pathToRoot(a, 9) (n = 9).
Call / stepComputationResult
left(2)2 · 2 = 4a[4] = D, B's left child
right(2)2 · 2 + 1 = 5a[5] = E, B's right child
parent(2)2 / 2 = 1a[1] = A, the root
left(4), right(4)8 and 9H and I, D's children
left(5), right(5)10 and 11 > noutside the array: E is a leaf
parent(9)9 / 2 = 4 (4.5 rounded down)D; parent(8) is also 4
pathToRoot pass 1i = 9 ≥ 1prints I; i = 4
Pass 2i = 4 ≥ 1prints D; i = 2
Pass 3i = 2 ≥ 1prints B; i = 1
Pass 4i = 1 ≥ 1prints A; i = 0
Loop ends0 ≥ 1 is falseoutput: I D B A
ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)
ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)
Figure 9.19: Top: E's child indices (i = 5) 10 and 11 fall outside the array; dashed cells mean “none.” Bottom: pathToRoot(a, 9) has printed I, D, B, A.

Two points stand out. First, a child index must be checked against the length: the left child exists if 2i ≤ n, the right if 2i + 1 ≤ n; otherwise the code reads past the end and throws ArrayIndexOutOfBoundsException. In an incomplete tree the cell may also be empty. Second, siblings share a parent: 8 and 9 both lead to 4, since integer division drops the right child's extra 1.

Each pass roughly halves the index, so the loop runs depth + 1 times, at most ⌊log2 n⌋ + 1 in a complete tree: O(log n). This is also why heap operations are fast: moving an element up takes at most height-many steps, like this loop.

Common mistake

Mixing up the 1-based and 0-based formulas. With the root at a[0], the left child is 2i + 1; with 2i, the root's “left child” is 2 · 0 = 0, itself. In a 0-based array, index 6 has parent ⌊(6 − 1) / 2⌋ = 2 and left child 2 · 6 + 1 = 13, not the 3 and 12 of the 1-based formulas. Fix the layout before writing code.

9.3.3Wasted space in the array

The array reserves a cell for every position, whatever the tree's shape: a node's index depends only on its position, so if the tree is not complete, the empty positions before it become empty cells.

ABCDE124816 A1B23C4567D89101112131415E16
Figure 9.20: A left-skewed five-node chain in an array: A at 1, B at 2, C at 4, D at 8, E at 16. Only 5 of 16 cells are used.

In Figure 9.20 each node is the previous one's left child, so the index doubles: 1, 2, 4, 8, 16, and five nodes need 16 cells, 11 empty. A right-skewed chain is worse: with 2i + 1 the indices are 1, 3, 7, 15, 31.

In general n nodes may need up to 2n − 1 cells: a chain of height n − 1 forces room for every position of the perfect tree of that height. A right-skewed chain of 30 nodes needs over a billion cells. Such exponential waste makes arrays impractical for trees of unpredictable shape.

Table 9.10: Pros and cons of the array representation.
ProsCons
No references; parent and child indices computed in O(1).Large waste for sparse or skewed trees: up to 2n − 1 cells.
No gaps in complete trees: the tightest use of memory.Fixed size; a growing tree must be copied into a larger array.

9.3.4Linked representation

Alternatively, each node is an object pointing to its children — the linked-list node adapted to trees, with two references, left and right, instead of one.

public class TreeNode {
    int data;
    TreeNode left;    // left child (null if none)
    TreeNode right;   // right child (null if none)

    public TreeNode(int data) {
        this.data = data;   // left = right = null
    }
}

data holds the node's value; left and right point to the children, or are null. The constructor sets only data; unassigned reference fields default to null, so a new node starts out as a leaf.

leftdatarightleft subtreeright subtree
Figure 9.21: A linked tree node: left reference, data, right reference, the references pointing to the roots of the left and right subtrees.

A small tree can be built by hand:

// build a small tree rooted at 9
TreeNode root = new TreeNode(9);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);

This creates root 9, its children 2 and 3, and, through root.left.left, 4 as the left child of 2. Node 2 has no right child, and 3 has no children.

Memory goes only to existing nodes: however skewed the tree, n nodes are n objects. Adding a node means creating an object and pointing a reference at it, O(1) once the spot is known. The price is access: we must follow references from the root, with no jumping by index, and moving to a parent requires an extra parent field.

ABCDEFGslash = null reference
Figure 9.22: A seven-node tree in the linked representation. Each node's reference cells point to its children; slashed cells are null, as are both references of the leaves D, E, F, G.

Count the references in Figure 9.22: seven nodes have 14 fields. Six are non-null, one per edge (Theorem 9.1), and 8 are null. This is no accident.

Theorem 9.5 (Number of null references)

In the linked representation with left and right references, every binary tree with n ≥ 1 nodes has exactly n + 1 null child references.

The 2n references include one non-null reference per edge, from parent to child, so n − 1 are non-null and 2n − (n − 1) = n + 1 are null. □

So more than half of a linked binary tree's reference fields are always empty: 51 of 100 in a 50-node tree. Threaded binary trees put this space to use, pointing empty left and right references to the previous and next node in inorder.

Common mistake

Accessing a node's fields without a null check: root.left.left throws NullPointerException if root.left is null. Before following a reference in tree code, ask whether it can be null; recursive methods usually check once, in the base case on the first line.

9.3.5Array or links?

The two representations complement each other, as Table 9.11 shows.

Table 9.11: Array and linked representations compared.
CriterionArray representationLinked representation
MemoryMinimal for a complete tree; up to 2n − 1 cells wasted for a sparse one2 extra references per node; no waste
Child / parent accessO(1), by index arithmeticChild O(1); parent needs an extra reference
kth node on level iO(1): index 2i + k − 1 (1-based, k ≥ 1)Requires walking from the root
GrowthCopied into a larger array when fullAdding a node is O(1) (if the position is known)
Best suited toComplete binary trees (heaps)General trees of changing shape (BSTs, expression trees)

The third row is another advantage of arrays: level i starts at index 2i, so its kth node is at 2i + k − 1; in Figure 9.18, the 3rd node of level 2 is F, at 4 + 3 − 1 = 6. With links we must walk level by level.

The rule is short: arrays for trees that stay complete, links for unpredictable shapes. A heap stays complete through insertions and deletions, so it lives in an array; a BST takes whatever shape its keys give it, so it uses links.

9.3.6Representing general trees

With unlimited children, a fixed number of reference fields will not do. Two solutions are common.

// 1) List of children
class GeneralNode {
    int data;
    List<GeneralNode> children
        = new ArrayList<>();
}
// 2) First child – next sibling
class SiblingNode {
    int data;
    SiblingNode firstChild;
    SiblingNode nextSibling;
}

In the first (GeneralNode), each node keeps its children in a list that grows as needed, and a for-each over children mirrors the ADT's children(p). It is intuitive, but every node carries a list object.

In the second (SiblingNode), each node has two references: its first child (firstChild) and its next sibling (nextSibling). To visit a node's children, go down to the first and walk right along the siblings; the child list becomes a linked list spread over the nodes.

ABCDEFG ABCDEFG
Figure 9.23: Left: a general tree; A's children are B, C, D, B's are E and F, D's is G. Right: the same tree in first-child (solid arrow) – next-sibling (dashed arrow) form.
Example 9.5

Give both references of every node in the first-child – next-sibling form of Figure 9.23.

  1. A. The root: firstChild = B, nextSibling = null.
  2. B, C, D. A's children form the chain B → C → D. B: first child E, next sibling C. C: no children, next sibling D. D: first child G, no next sibling.
  3. E, F, G. E: no children, next sibling F. F and G: both null.
  4. Count. Of 14 fields, six are non-null — A→B, B→E, B→C, C→D, D→G, E→F — which is n − 1 = 6.

Call the first-child reference “left” and the next-sibling reference “right,” and any general tree becomes a binary tree. Rotated 45 degrees, the right half of Figure 9.23 is one: B is A's left child; E and C are B's children; D is C's right child, F is E's right child, G is D's left child. Binary tree algorithms thus extend to general trees, and a forest becomes one binary tree if its roots are treated as siblings.

Use the child list if a node's kth child is accessed often: children.get(k) is O(1), against k steps along the sibling chain. The sibling form uses a small fixed amount of memory per node and needs no list object.

9.4Tree traversals

An array or list has one natural order of visiting: front to back. A tree has none. At a node, do we process the node or its subtrees first? Which subtree first? Or do we go level by level? Each answer gives a traversal, and each traversal naturally solves a different problem.

9.4.1What is a traversal?

Definition 9.5

A traversal visits every node of a tree exactly once, in a particular order. A “visit” is whatever work is done at the node: printing it, adding it to a sum, copying or deleting it.

Traversals come in two families. Depth-first traversals follow a branch to its end before backing up to the next; the three members differ only in when the root is visited relative to its subtrees. The breadth-first traversal, level order, scans level by level: the root, its children, its grandchildren.

1234567
Figure 9.24: The example tree for traversals: the perfect binary tree numbered 1 to 7, with root 1, children 2 and 3, and leaves 4 to 7.
Table 9.12: The four traversals and their outputs on the tree of Figure 9.24.
TraversalOrderOutputTypical use
Preorderroot → left → right1 2 4 5 3 6 7Copying a tree, prefix expressions
Inorderleft → root → right4 2 5 1 6 3 7Visiting a BST in sorted order
Postorderleft → right → root4 5 2 6 7 3 1Deletion, computing size and height
Level orderlevel by level, left to right1 2 3 4 5 6 7Level-based operations

The depth-first traversals are named for where the root falls: before (pre), between (in), or after (post) its subtrees, the left subtree always preceding the right. They follow the recursive definition — act at the root, apply the same traversal to each subtree — so when working one out by hand, treat each subtree as a small tree of its own.

9.4.2Preorder traversal

A preorder traversal has three steps:

  1. Visit the root.
  2. Traverse the left subtree in preorder.
  3. Traverse the right subtree in preorder.
visit(root);
preorder(root.left);
preorder(root.right);

A node precedes all its descendants in the output. On Figure 9.24: visit 1; enter the left subtree and visit its root 2; visit 2's subtrees, 4 and 5. Then the right subtree: 3, 6, 7. Output: 1 2 4 5 3 6 7.

1234567Output: 1 2 4nowvisitedwaiting 1234567Output: 1 2 4 5 3 6 7nowvisitedwaiting
Figure 9.25: Preorder. Left: after 1 and 2, the leaf 4 is visited; output 1 2 4. Right: done; 1 2 4 5 3 6 7.

Preorder fits whenever a node must be handled before its children. Copying a tree is the clearest case: children cannot be attached before their parent's copy exists. Writing a tree to a file uses preorder too, so the reader meets each node after its parent and can rebuild the tree. A table of contents is a preorder traversal, and on expression trees preorder gives prefix notation, operators before operands.

9.4.3Inorder traversal

In an inorder traversal the root is visited between its subtrees:

  1. Traverse the left subtree in inorder.
  2. Visit the root.
  3. Traverse the right subtree in inorder.
inorder(root.left);
visit(root);
inorder(root.right);

A node comes after its whole left subtree and before its right one, so the output starts not with the root but with the leftmost node. On Figure 9.24 we go from 1 to 2 to 4; 4's subtrees are empty, so 4 is visited, then 2, then 5. With the left subtree done, 1 is visited; in the right subtree we again go far left: 6, 3, 7. Output: 4 2 5 1 6 3 7.

1234567Output: 4 2 5 1nowvisitedwaiting 1234567Output: 4 2 5 1 6 3 7nowvisitedwaiting
Figure 9.26: Inorder. Left: the left subtree (4, 2, 5) is done and the root 1 visited. Right: done; 4 2 5 1 6 3 7.

Geometrically, inorder lists the nodes in their left-to-right order in the drawing, as if each were dropped straight down onto a line: 4, 2, 5, 1, 6, 3, 7 in Figure 9.24.

Its key use is in binary search trees. Smaller keys lie left of every node and larger ones right, so inorder outputs the keys in increasing order; conversely, a binary tree with sorted inorder output (and distinct keys) is a BST. On expression trees inorder gives infix notation, minus the parentheses.

Common mistake

Starting inorder at the root. The root follows its entire left subtree; the first node written is the leftmost, reached by going left as far as possible. Also remember that the leftmost node may have a right subtree: after writing it, enter that subtree and again go far left.

9.4.4Postorder traversal

In a postorder traversal the root is visited after both subtrees are finished:

  1. Traverse the left subtree in postorder.
  2. Traverse the right subtree in postorder.
  3. Visit the root.
postorder(root.left);
postorder(root.right);
visit(root);

Every node follows all its descendants, and the root comes last. On Figure 9.24: the leftmost leaf 4, then 5, then 2; the right subtree gives 6, 7, 3; the root 1 is last. Output: 4 5 2 6 7 3 1.

1234567Output: 4 5 2nowvisitedwaiting 1234567Output: 4 5 2 6 7 3 1nowvisitedwaiting
Figure 9.27: Postorder. Left: 2 visited after 4 and 5; output 4 5 2. Right: the root last; 4 5 2 6 7 3 1.

Postorder fits whenever a node's work depends on its children's results. Deleting a tree must remove children first, or the references to them are lost; likewise a nonempty folder cannot be deleted before its contents. A folder's total disk usage, a tree's node count, and its height are all computed in postorder. On expression trees postorder gives postfix notation, the order of evaluation.

A quick check relates the outputs: the root is first in preorder, last in postorder, and between its subtrees in inorder — in Table 9.12, 1 starts, ends, and splits the outputs. This observation also underlies rebuilding a tree from two traversals.

9.4.5Three traversals, one tour: the Euler tour

One picture unites the three depth-first traversals. Walk around the tree from the left of the root, keeping the edges at your left hand. This Euler tour passes every edge twice, down and up, and meets every node three times: on its left, below it, and on its right.

ABCDE
ABCDE1234512345Left mark → preorder:A B D E CBelow mark → inorder:D B E A C
ABCDE123451234512345Left mark → preorder:A B D E CBelow mark → inorder:D B E A CRight mark → postorder:D E B C A
Figure 9.28: The Euler tour of a five-node tree (root A; B's children D and E; C is A's right child). Top: the path. Middle: passes on the left (preorder: A B D E C) and below (inorder: D B E A C) numbered. Bottom: passes on the right (postorder: D E B C A) added.

The pass at which we write a node picks the traversal.

  • On its left, on first arrival: preorder, A B D E C.
  • Below it, between its subtrees: inorder, D B E A C.
  • On its right, after both subtrees: postorder, D E B C A.

At a leaf the three passes are consecutive, since its subtrees are empty, so leaves keep the same relative order in all three traversals (D, E, C); only internal nodes move. The three traversals are moments of one tour. The tour takes 2(n − 1) edge steps, O(n) — another reason all three are O(n).

Some jobs need all three passes. Writing an expression fully parenthesized opens “(” on entering a subtree, writes the operator below, and closes “)” on leaving.

9.4.6Level-order traversal

A level-order traversal scans each level left to right, top down — the tree version of breadth-first search (BFS) on graphs.

level 0level 1level 21234567Output: 1 2 3 4 5 6 7
Figure 9.29: Level order: 1 on level 0; 2, 3 on level 1; 4–7 on level 2. Dashed arrows show the scan; output 1 2 3 4 5 6 7.

Level order ignores subtree structure: after 2 it moves to 2's sibling 3, not its child 4, and 2's children wait until level 1 is done. Recursion, which finishes one subtree before starting the next, cannot express this jumping back and forth.

The solution is a first-in, first-out queue. A visited node's children join the back of the queue, so earlier nodes' children come out earlier, and each level enters the queue before the next.

Level order suits jobs where levels mean something: printing level by level, per-level sums or maxima, the tree's width, or the leaf nearest the root. For that last job it is ideal: the first leaf to leave the queue is among the nearest, and the traversal can stop without visiting deeper nodes.

Common mistake

Confusing level order with preorder. Both start at the root, yet on Figure 9.24 they differ by the third element: 1 2 4 versus 1 2 3. Preorder dives into a branch; level order finishes siblings first. Writing level order with a stack is the same confusion in code: the output becomes depth-first.

9.5Coding the traversals

Traversals are defined recursively, and their shortest code is recursive. Behind the recursion sits an invisible structure, the call stack: each call pushes a stack frame holding its parameters, local variables, and return point. We first trace the recursive traversals on the call stack, then replace it with an explicit Deque used as a stack, and finally code level order with a queue.

All code uses TreeNode from the linked representation. The recursive traces use the four-node tree rooted at 9: 9's children are 2 and 3, and 2's left child is 4 (height 2).

9.5.1Recursive preorder and the call stack

public void preorder(TreeNode root) {
  if (root == null) {
    return;
  }
  System.out.print(root.data + " ");
  preorder(root.left);
  preorder(root.right);
}

If root == null we are in an empty subtree and return at once: the base case. Otherwise the method prints the root (line 5), recurses on the left subtree (line 6), then on the right (line 7), and at the closing brace (line 8) its frame is popped.

A null base case keeps the code simple — leaves need no special case, since their two calls return at once — at the price of one call per null child. By Theorem 9.5 there are n + 1 of those, so 2n + 1 calls in all: 9 for our tree.

Table 9.13: Trace of preorder(9). The call stack is written bottom to top; “ln 6” and “ln 7” give the line from which a call was made.
StepCall stackEventOutput
1preorder(9)prints 99
29 · ln 6 | preorder(2)prints 29 2
39 · ln 6 | 2 · ln 6 | preorder(4)prints 49 2 4
4… | 4 · ln 6 | preorder(null)base case, return9 2 4
5… | 4 · ln 7 | preorder(null)base case, return9 2 4
69 · ln 6 | 2 · ln 6 | preorder(4)4 finishes (line 8)9 2 4
79 · ln 6 | 2 · ln 7 | preorder(null)2's right is empty9 2 4
89 · ln 6 | preorder(2)2 finishes9 2 4
99 · ln 7 | preorder(3)prints 39 2 4 3
10–119 · ln 7 | 3 · ln 6 / ln 7 | preorder(null)both of 3's children are empty9 2 4 3
129 · ln 7 | preorder(3)3 finishes9 2 4 3
13preorder(9)9 finishes, the stack empties9 2 4 3
nullnullnullnullnull9234running nowon the call stackprintednot printed yet
nullnullnullnullnull9234running nowon the call stackprintednot printed yet
Figure 9.30: Recursive preorder. Top, step 4: preorder(4.left) is at the base case; frames for 9, 2, 4 wait. Bottom, last step: output 9 2 4 3.

Note the stack depth. At steps 4 and 5 it peaks at four frames — 9, 2, 4, and 4's null child. Depth is set by the path to the deepest null child: h + 1 nodes plus one null call, at most h + 2 frames; here 2 + 2 = 4.

Time is O(n), one constant-work call per node and per null. Extra memory is the stack depth, O(h): O(log n) if balanced, O(n) if skewed.

9.5.2Recursive inorder

public void inorder(TreeNode root) {
  if (root == null) {
    return;
  }
  inorder(root.left);
  System.out.print(root.data + " ");
  inorder(root.right);
}

Only the print has moved, between the two calls. A call no longer prints on entry; it traverses its whole left subtree first and prints on line 6, after the left call returns.

Table 9.14: Trace of inorder(9). Left calls are made from line 5, right calls from line 7.
StepCall stackEventOutput
1–39 · ln 5 | 2 · ln 5 | inorder(4)9, 2, 4 are called; none printed yet—
4… | 4 · ln 5 | inorder(null)4's left is empty—
59 · ln 5 | 2 · ln 5 | inorder(4)left done: prints 44
6–7… | 4 · ln 7 | inorder(null)4's right is empty; 4 finishes4
89 · ln 5 | inorder(2)left done: prints 24 2
9–109 · ln 5 | 2 · ln 7 | inorder(null)2's right is empty; 2 finishes4 2
11inorder(9)left done: prints 94 2 9
12–139 · ln 7 | 3 · ln 5 | inorder(null)3 is called; 3's left is empty4 2 9
149 · ln 7 | inorder(3)prints 34 2 9 3
15–17inorder(9)3's right is empty; 3 and 9 finish4 2 9 3
nullnullnullnullnull9234running nowon the call stackprintednot printed yet
Figure 9.31: Recursive inorder, step 5: 4's empty left subtree is done and 4 printed; frames for 2 and 9 wait for their left calls.

The output is 4 2 9 3: 9 after its left subtree, before its right. 9's frame, created at step 1, waits on line 5 until step 11 to print. A frame on the stack marks unfinished work.

9.5.3Recursive postorder

public void postorder(TreeNode root) {
  if (root == null) {
    return;
  }
  postorder(root.left);
  postorder(root.right);
  System.out.print(root.data + " ");
}

Now the print follows both calls (lines 5 and 6), on line 7: a value is printed only after both subtrees are fully traversed.

Table 9.15: Trace of postorder(9). Left calls are made from line 5, right calls from line 6.
StepCall stackEventOutput
1–39 · ln 5 | 2 · ln 5 | postorder(4)9, 2, 4 are called—
4… | 4 · ln 5 | postorder(null)4's left is empty—
5… | 4 · ln 6 | postorder(null)4's right is empty—
6–79 · ln 5 | 2 · ln 5 | postorder(4)both subtrees done: prints 4; 4 finishes4
89 · ln 5 | 2 · ln 6 | postorder(null)2's right is empty4
9–109 · ln 5 | postorder(2)prints 2; 2 finishes4 2
11–139 · ln 6 | 3 · ln 5 / ln 6 | postorder(null)3 is called; both children empty4 2
14–159 · ln 6 | postorder(3)prints 3; 3 finishes4 2 3
16–17postorder(9)prints 9; the stack empties4 2 3 9
nullnullnullnullnull9234running nowon the call stackprintednot printed yet
Figure 9.32: Recursive postorder, step 6: both (empty) subtrees of 4 are done and 4 printed; 9 and 2 are not yet.

The output is 4 2 3 9. Side by side, the three traces make and return the same calls in the same order — 9, 2, 4, null, null, … — and differ only in where the print falls. This is the Euler tour in code: entering a call is passing on the node's left, the gap between calls passing below, and leaving passing on its right.

Common mistake

Forgetting the base case. Without if (root == null) return;, the first leaf calls preorder(root.left) with null and reading root.data throws NullPointerException; putting the check after the print fails the same way. Handle the empty tree before touching a node's fields.

9.5.4Preorder with a stack

The system's call stack is limited: with Java's defaults a StackOverflowError comes after a few thousand to a few tens of thousands of frames, so recursion crashes on a chain of hundreds of thousands of nodes. An explicit java.util.Deque stack does the same job in heap memory, filling up only when memory runs out.

public void preorderIterative(TreeNode root) {
  if (root == null) return;
  Deque<TreeNode> stack = new ArrayDeque<>();
  stack.push(root);
  while (!stack.isEmpty()) {
    TreeNode temp = stack.pop();
    System.out.print(temp.data + " ");
    if (temp.right != null)
      stack.push(temp.right);  // right first
    if (temp.left != null)
      stack.push(temp.left);   // left on top
  }
}

Preorder is the easiest: a node precedes its children, so it can be printed as soon as it is popped. Each pass pops, prints, and pushes the children, right first, then left, so the left child sits on top of the LIFO stack and its whole subtree is done before the right child's turn.

Table 9.16: Trace of preorderIterative on the tree of Figure 9.24. The stack is written bottom to top.
PasspopPushedStack after the passOutput
start—1[1]—
113, 2[3, 2]1
225, 4[3, 5, 4]1 2
34—[3, 5]1 2 4
45—[3]1 2 4 5
537, 6[7, 6]1 2 4 5 3
66—[7]1 2 4 5 3 6
77—[]1 2 4 5 3 6 7
1234567Stack354topnowon stackprinted 1234567Stackemptynowon stackprinted
Figure 9.33: Preorder with a stack. Left: after pass 2, with 1 and 2 printed, the stack holds 3, 5, 4. Right: the stack is empty; the output 1 2 4 5 3 6 7 matches the recursive version.

The stack is the recursion's to-do list: after pass 2, [3, 5, 4] means “4's subtree, then 5's, then 3's,” information the recursive version kept in the lines where frames 2 and 9 waited.

Each node is pushed and popped once: n passes, O(n). The stack holds at most one waiting right sibling per level plus the top node, here at most 3 (after pass 2): O(h) extra memory.

Common mistake

Pushing left before right. The right child then pops first and the output becomes root, right, left: 1 3 7 6 2 5 4 on Figure 9.24. Push right first so left stays on top.

9.5.5Inorder with a stack

In inorder a node cannot be printed when pushed, since its left subtree comes first; the stack holds nodes “to print later.” Two moves alternate: while temp is a node, push it and go left; when temp is null, the top node's left subtree is done, so pop it, print it, and go to its right subtree.

public void inorderIterative(TreeNode root) {
  if (root == null) return;
  Deque<TreeNode> stack = new ArrayDeque<>();
  TreeNode temp = root;
  while (!stack.isEmpty() || temp != null) {
    if (temp != null) {
      stack.push(temp);
      temp = temp.left;
    } else {
      temp = stack.pop();
      System.out.print(temp.data + " ");
      temp = temp.right;
    }
  }
}

The loop condition has two parts, !stack.isEmpty() || temp != null, because right after the root is printed the stack may be empty while temp points to the untraversed right subtree. The trace, on the tree where 1's children are 2 and 3, 2's are 4 and 5, and 3 has only a left child 6, includes this moment.

Table 9.17: Trace of inorderIterative (tree: 1(2(4, 5), 3(6, —))). Stack bottom to top.
StepMoveStacktempOutput
0start[]1—
1push 1, go left[1]2—
2push 2, go left[1, 2]4—
3push 4, go left[1, 2, 4]null—
4pop 4, print, go right[1, 2]null4
5pop 2, print, go right[1]54 2
6push 5, go left[1, 5]null4 2
7pop 5, print, go right[1]null4 2 5
8pop 1, print, go right[]34 2 5 1
9push 3, go left[3]64 2 5 1
10push 6, go left[3, 6]null4 2 5 1
11pop 6, print, go right[3]null4 2 5 1 6
12pop 3, print, go right[]null4 2 5 1 6 3
123456Stack124topnowon stackprinted 123456Stackemptynowon stackprinted
Figure 9.34: Inorder with a stack. Left, step 3: 1, 2, 4 on the stack, temp = null, nowhere left to go. Right, step 8: 1 printed, stack empty, but temp is 3, so the loop continues.

At the end of step 8 the stack is empty; with only !stack.isEmpty() the loop would stop and 3 and 6 would never print. temp != null catches the waiting right subtree. The loop ends only when both are exhausted, after step 12, with output 4 2 5 1 6 3.

Each node is pushed and popped once and each null child reached once: O(n). The stack holds the nodes on the path to temp with unfinished left subtrees, at most h + 1: O(h) extra memory.

9.5.6Postorder with a stack

Postorder is hardest: a node prints only when both subtrees are done. A node on top of the stack has finished its left subtree, but we cannot tell about the right, so the code looks with peek before popping.

public void postorderIterative(TreeNode root) {
  TreeNode current = root;
  Deque<TreeNode> stack = new ArrayDeque<>();
  while (current != null || !stack.isEmpty()) {
    if (current != null) {
      stack.push(current);
      current = current.left;
    } else {
      TreeNode temp = stack.peek().right;
      if (temp == null) {
        temp = stack.pop();
        System.out.print(temp.data + " ");
        while (!stack.isEmpty()
           && temp == stack.peek().right) {
          temp = stack.pop();
          System.out.print(temp.data + " ");
        }
      } else {
        current = temp;
      }
    }
  }
}

The logic has three parts.

  • Going left. If current is not null, push it and go left, as in inorder.
  • Entering the right subtree. Otherwise look at the top node's right child (stack.peek().right). If it exists, the node must wait: set current to it and enter that subtree.
  • Printing and climbing. With no right child, the top node is finished: pop and print it. The inner while then looks up: if the printed node is the new top's right child, that parent is finished too and is popped and printed, and so on up the chain.

temp == stack.peek().right tests reference equality — the same object? — asking “are we returning from this parent's right subtree?” If so, the parent prints; if the printed node was a left child, the parent's right subtree is still ahead and the loop stops. The trace uses the tree where 1's children are 2 and 3, 2's are 4 and 5, and 3 has only a right child, 6.

Table 9.18: Trace of postorderIterative (tree: 1(2(4, 5), 3(—, 6))). Stack bottom to top.
StepEventStackOutput
1–3push 1, 2, 4; current = 4.left = null[1, 2, 4]—
4peek() = 4, no right child: pop and print 4[1, 2]4
5inner loop: 4 is not 2's right child (2.right = 5); stop[1, 2]4
6peek() = 2, right child 5: current = 5[1, 2]4
7push 5; current = null[1, 2, 5]4
8peek() = 5, no right child: print 5[1, 2]4 5
9inner loop: 5 is 2's right child: print 2[1]4 5 2
10inner loop: 2 is not 1's right child (1.right = 3); stop[1]4 5 2
11peek() = 1, right child 3: current = 3[1]4 5 2
12push 3; current = 3.left = null[1, 3]4 5 2
13peek() = 3, right child 6: current = 6[1, 3]4 5 2
14push 6; current = null[1, 3, 6]4 5 2
15peek() = 6, no right child: print 6[1, 3]4 5 2 6
16inner loop: 6 is 3's right child: print 3[1]4 5 2 6 3
17inner loop: 3 is 1's right child: print 1[]4 5 2 6 3 1
18–19stack empty: inner loop stops; current is null, outer loop ends[]4 5 2 6 3 1
123456Stack1topnowon stackprinted 123456Stackemptynowon stackprinted
Figure 9.35: Postorder with a stack. Left, step 9: after 5, the inner loop saw 5 is 2's right child and printed 2; only 1 remains. Right: done; 4 5 2 6 3 1.

The end of the trace shows the inner loop at work: printing 6 at step 15 completes 3 (6 is its right child) and then 1 (3 is its right child) — one leaf finishing a whole chain of parents. 3's missing left child is harmless: at step 12 current = 3.left is simply null.

The cost is O(n): each node is pushed and popped once, and every peek leads to a pop or a right subtree. The stack holds the current root-to-node path, at most h + 1 nodes. A shortcut exists: stack-based preorder with the pushes reversed (left, then right) outputs root, right, left, and reversing that with a second stack gives left, right, root — easy, but O(n) extra memory.

9.5.7Level order with a queue

public void levelOrder(TreeNode root) {
  if (root == null) return;
  Queue<TreeNode> queue = new LinkedList<>();
  queue.add(root);
  while (!queue.isEmpty()) {
    TreeNode temp = queue.poll();
    System.out.print(temp.data + " ");
    if (temp.left != null)
      queue.add(temp.left);
    if (temp.right != null)
      queue.add(temp.right);
  }
}

This mirrors stack-based preorder with three changes: a Queue (a LinkedList) replaces the stack, add (to the back) and poll (from the front) replace push and pop, and children are added in natural order, left first, since FIFO returns them in that order.

The trace uses the tree rooted at 4, with children 3 and 7; 3's children are 5 and 8, and 7 is a leaf.

Table 9.19: Trace of levelOrder (tree: 4(3(5, 8), 7)). The queue is written front to back.
PasspollAddedQueue after the passOutput
start—4[4]—
143, 7[3, 7]4
235, 8[7, 5, 8]4 3
37—[5, 8]4 3 7
45—[8]4 3 7 5
58—[]4 3 7 5 8
43758Queue758frontbacknowin queueprinted 43758Queueemptynowin queueprinted
Figure 9.36: Level order with a queue. Left: after pass 2, 3's children 5 and 8 are queued behind 7. Right: the queue is empty; 4 3 7 5 8.

Pass 2 is the essence of level order: 3's children go behind 7, which shares 3's level and is processed first. Preorder would give 4 3 5 8 7; level order gives 4 3 7 5 8.

Each node enters and leaves once: O(n). Memory, though, depends on the width w, the size of the most crowded level, since the queue spans at most two consecutive levels: O(w), here at most 3. A perfect tree's last level has about n/2 nodes, so level order needs O(n) there, while stack-based inorder needs O(log n).

Common mistake

Confusing add() with push(). LinkedList is both a queue and a stack: pop() removes from the front and push() inserts there. Writing push for add puts children at the front, turning the queue into a stack and the output into something other than level order. Declaring the variable as Queue<TreeNode> rules out stack methods at compile time.

9.6Applications of traversals

Traversals do more than print. Nearly every tree algorithm visits nodes in some order and does work at each visit; this section applies that pattern to real problems.

9.6.1Expression trees

An arithmetic expression is naturally a tree. In (3 + 4) × (5 − 2) the multiplication happens last, and each of its operands is a subexpression.

Definition 9.6

An expression tree is a binary tree whose internal nodes are operators and whose leaves are operands (numbers or variables). With binary operators every internal node has two children, so the tree is full. An internal node's value is its operator applied to the values of its left and right subtrees.

×+−3452
Figure 9.37: The tree of (3 + 4) × (5 − 2): multiplication at the root, 3 plus 4 on the left, 5 minus 2 on the right.

The tree needs no parentheses: addition and subtraction sit deeper, so they are computed first. Precedence and grouping are built into the shape, and the three depth-first traversals give three notations.

Table 9.20: Traversals of the tree in Figure 9.37.
TraversalOutputNotation
Preorder× + 3 4 − 5 2prefix
Inorder3 + 4 × 5 − 2; with parentheses (3 + 4) × (5 − 2)infix
Postorder3 4 + 5 2 − × → 7 × 3 = 21postfix

Prefix and postfix need no parentheses; the order of symbols alone fixes the expression. Unparenthesized inorder does not: 3 + 4 × 5 − 2 happens to equal 3 + 20 − 2 = 21 under the usual precedence, but it is not the tree's expression. Correct infix puts every internal node's subtree in parentheses — an Euler tour writing “(” on entry and “)” on exit.

Common mistake

Taking the inorder output as the infix expression. Without parentheses it may describe a different tree: the tree for ((6 + 2) × 3) − (8 / 4) gives 6 + 2 × 3 − 8 / 4, which evaluates to 6 + 6 − 2 = 10, while the tree's value is 22.

9.6.2Code trace: evaluate()

Evaluation is postorder: an operator needs both operand values first. The code assumes an ExprNode class holding a number (value) at a leaf and an operator character (op) at an internal node.

int evaluate(ExprNode node) {
  if (node.left == null && node.right == null)
    return node.value;           // leaf: operand
  int a = evaluate(node.left);
  int b = evaluate(node.right);
  switch (node.op) {             // internal node: operator
    case '+': return a + b;
    case '-': return a - b;
    case '*': return a * b;
    default:  return a / b;
  }
}

A node with no children is an operand, and its number is returned: the base case. Otherwise the left subtree's value goes into a (line 4), the right's into b (line 5), and the switch applies the operator. Order matters for subtraction and division: in a - b, a is the left operand. An expression tree is full, so the one-child case never arises.

Table 9.21: Trace of evaluate(×). Call stack bottom to top; “ln 4” is the call for the left operand, “ln 5” for the right.
StepCall stackEvent
1evaluate(×)internal node; left operand first: evaluate(+)
2× · ln 4 | evaluate(+)internal node; left operand first: evaluate(3)
3× · ln 4 | + · ln 4 | evaluate(3)leaf: returns 3
4–5× · ln 4 | + · ln 5 | evaluate(4)a = 3; right operand is a leaf: returns 4
6× · ln 4 | evaluate(+)b = 4; returns 3 + 4 = 7
7–8× · ln 5 | evaluate(−)a = 7; right operand evaluate(−)
9–11× · ln 5 | − · ln 4 / ln 5 | evaluate(5), evaluate(2)leaves 5 and 2 return
12× · ln 5 | evaluate(−)returns 5 − 2 = 3
13evaluate(×)b = 3; returns 7 × 3 = 21
×+−3452↑ 3↑ 4↑ 7 ×+−3452↑ 3↑ 4↑ 7↑ 5↑ 2↑ 3↑ 21
Figure 9.38: Tracing evaluate. Left, step 6: 3 and 4 passed up, + returned 7. Right, last step: − returned 3, the root 21. Arrowed numbers are return values.

Values flow upward, and the operators apply in the order +, −, × — postorder, matching the postfix form 3 4 + 5 2 − ×. The stack-based postfix evaluator does the same work, with the call stack as its stack. Each node is processed once: O(n) time, O(h) extra memory.

9.6.3Finding the largest value

An unordered binary tree hides its maximum anywhere, so every node must be checked. Recursively, the largest value is the largest of the root's value and the maxima of the two subtrees.

public int findMax(TreeNode root) {
  if (root == null) {
    return Integer.MIN_VALUE;
  }
  int result = root.data;
  int left = findMax(root.left);
  int right = findMax(root.right);
  if (left > result)
    result = left;
  if (right > result)
    result = right;
  return result;
}

An empty tree has no largest value, but its return value must not distort the comparison. Integer.MIN_VALUE (−231) never wins left > result, so empty subtrees have no effect. The body is postorder: subtree results first, then the root.

Table 9.22: Trace of findMax (tree: 4(3(5, 8), 7)). MIN stands for Integer.MIN_VALUE.
Callresult at firstleftrightReturns
findMax(5)5MINMIN5
findMax(8)8MINMIN8
findMax(3)35 (> 3: result = 5)8 (> 5: result = 8)8
findMax(7)7MINMIN7
findMax(4)48 (> 4: result = 8)7 (≤ 8)8
nullMINnullMINnullnullnullnull43758↑ 5 nullMINnullMINnullMINnullMINnullMINnullMIN43758↑ 5↑ 8↑ 8↑ 7↑ 8
Figure 9.39: Tracing findMax. Left: 5's empty subtrees returned MIN, and 5 returned itself. Right: all values passed up; 3's subtree gives 8, 7 itself, the root 8.

The table lists calls in the order they finish — 5, 8, 3, 7, 4, postorder. With one call per node and per null child, 11 calls in all, time is O(n). In a BST we would just go right from the root, O(h); an unordered tree forces a look at every node.

Common mistake

Returning 0 for the empty tree. In an all-negative tree — nodes −5 and −3, say — the method returns 0, a value in no node. The base value must be the identity element of the combining operation: Integer.MIN_VALUE for a maximum, 0 for a sum, 1 for a product.

9.6.4Recursive tree functions

evaluate and findMax share a pattern: a base value for the empty tree, otherwise the subtrees' results combined with the root. The pattern computes many basic properties in a few lines.

int size(TreeNode node) {            // number of nodes
    if (node == null) return 0;
    return 1 + size(node.left) + size(node.right);
}

int height(TreeNode node) {          // number of edges
    if (node == null) return -1;     // empty tree: -1
    return 1 + Math.max(height(node.left),
                        height(node.right));
}

int countLeaves(TreeNode node) {
    if (node == null) return 0;
    if (node.left == null && node.right == null) return 1;
    return countLeaves(node.left) + countLeaves(node.right);
}
  • size(node) counts nodes: 0 for an empty tree, otherwise 1 for the root plus size(node.left) and size(node.right).
  • height(node) measures edges, and its base case returns −1. Odd as it looks, this makes a leaf's height 1 + max(−1, −1) = 0; returning 0 would give leaves height 1, measuring nodes.
  • countLeaves(node) has two base cases: 0 for an empty tree, 1 for a node with two null children. Any other node returns the sum of its subtrees' counts, not counting itself.

On the four-node tree rooted at 9 (children 2 and 3; 4 is 2's left child) they give size = 4, height = 2, and countLeaves = 2 (leaves 4 and 3). Each processes every node once: O(n) time, O(h) stack space.

9.6.5Code trace: size()

The trace shows values carried up from the subtrees. All recursive calls come from line 3; Java evaluates left to right, so size(node.left) runs before size(node.right).

Table 9.23: Trace of size(9) (tree: 9(2(4, —), 3)).
StepCallEventReturns
1–3size(9), size(2), size(4)each calls its left subtree first—
4size(4.left)empty tree0
5–6size(4.right)left gave 0; right is empty too0
7size(4)1 + 0 + 01
8–9size(2.right)left gave 1; right is empty0
10size(2)1 + 1 + 02
11–15size(3)both children empty; 1 + 0 + 01
16–17size(9)1 + 2 + 14
null0null0null0nullnull9234↑ 1↑ 2running nowwaiting on stackhas returnednot called yet
null0null0null0null0null09234↑ 1↑ 2↑ 1↑ 4running nowwaiting on stackhas returnednot called yet
Figure 9.40: Tracing size. Top, step 10: 4's subtree returned 1, 2's returned 2. Bottom, last step: 3 returned 1 and the root 1 + 2 + 1 = 4. The 0s under null children are the empty trees' returns.

Nine calls are made, for four nodes and five null children: 2n + 1 = 9. Each node returns its subtree's size — 1 for 4, 2 for 2, 1 for 3, 4 for 9 — so one postorder pass sizes every subtree. Stored in the nodes, these sizes answer questions like “what is the kth smallest key?” quickly.

9.6.6Code trace: height()

Here the left call comes from line 3 and the right from line 4, the two arguments of Math.max being on separate lines. Same tree.

Table 9.24: Trace of height(9) (tree: 9(2(4, —), 3)).
StepCallComputationReturns
1–3height(9), (2), (4)each calls its left subtree first—
4–6height(4.left), height(4.right)empty trees−1, −1
7height(4)1 + max(−1, −1)0
8–9height(2.right)empty tree−1
10height(2)1 + max(0, −1)1
11–16height(3)1 + max(−1, −1)0
17height(9)1 + max(1, 0)2
null-1null-1null-1null-1null-19234↑ 0↑ 1↑ 0↑ 2running nowwaiting on stackhas returnednot called yet
Figure 9.41: The last step of the height trace. Empty trees returned −1, the leaves 4 and 3 returned 0; 2 has height 1 and the root 1 + max(1, 0) = 2.

The result is 2, the two edges of 9–2–4. The call structure equals that of size; only the combination changes, maximum instead of sum. That is the pattern's power: changing the base value and the combining operation yields node sums, minima, occurrence counts, and much more from one skeleton.

9.6.7Code trace: countLeaves()

countLeaves returns at a leaf without further calls, so the leaves' null children are never visited and the trace is shorter.

Table 9.25: Trace of countLeaves(9) (tree: 9(2(4, —), 3)).
StepCallEventReturns
1countLeaves(9)not a leaf; left first—
2countLeaves(2)not a leaf; left first—
3countLeaves(4)both children null: a leaf1
4countLeaves(2.right)empty tree0
5countLeaves(2)1 + 01
6countLeaves(3)a leaf1
7countLeaves(9)1 + 12
nullnullnull0nullnull9234↑ 1↑ 1↑ 1↑ 2running nowwaiting on stackhas returnednot called yet
Figure 9.42: The last step of the countLeaves trace. Leaves 4 and 3 returned 1, 2's empty right subtree 0; the root gets 1 + 1 = 2.

Only five calls are made — four nodes plus 2's empty right child — against nine for size, because the leaf check stops descent at leaves. That check is also essential: without it the method returns 0 at empty trees and never produces a 1, so every tree would have 0 leaves. A leaf is what we count, so it needs its own base case.

Common mistake

Writing only the node == null base case in countLeaves, or putting the leaf check before it. The first always returns 0; the second may read node.left with node null and throw NullPointerException. Keep the two base cases in the order shown.

9.6.8Rebuilding a tree from its traversals

One traversal does not determine a tree: preorder 1 2 fits 2 as either child of 1. Two suitable traversals together, with distinct values, determine it uniquely.

Theorem 9.6 (Rebuilding from traversals)

A binary tree with distinct node values can be built in exactly one way from its inorder and preorder outputs (or from its inorder and postorder outputs). Preorder and postorder outputs, however, do not determine a tree that contains nodes with one child.

The method follows from the definitions. The first preorder element is the root; in the inorder output, the elements to its left form the left subtree and those to its right the right subtree. The left subtree's size then tells which preorder elements belong to it, and the step recurses on both subtrees. With postorder, the root is the last element instead.

Preorder plus postorder fixes the root but cannot separate the subtrees: A B and B A fit B as A's left or right child. Only inorder distinguishes them, writing a left child before the root and a right child after. In full trees there are no one-child nodes, so the ambiguity vanishes.

int p = 0; // next root in pre[]
TreeNode build(int[] pre, int[] in, int lo, int hi) {
    if (lo > hi) return null;         // empty range
    TreeNode node = new TreeNode(pre[p++]);
    int m = lo; while (in[m] != node.data) m++;
    node.left = build(pre, in, lo, m - 1);  // left part
    node.right = build(pre, in, m + 1, hi); // right part
    return node;
}

build(pre, in, lo, hi) builds the subtree for in[lo..hi] and returns its root, or null for an empty range (lo > hi). Otherwise pre[p] is the subtree's root: a node is created and p incremented. As a field, p is shared by all calls and sweeps the preorder array once. The root's index m in the inorder range splits it into in[lo..m−1] (left) and in[m+1..hi] (right). The left subtree must be built first, since its elements precede the right's in preorder.

Example 9.6

Build the tree from pre = [1, 2, 4, 5, 3, 6] and in = [4, 2, 5, 1, 3, 6], starting with build(pre, in, 0, 5).

  1. pre[0] = 1. Root 1, at m = 3 in in: left subtree in[0..2] = 4, 2, 5, right in[4..5] = 3, 6.
  2. pre[1] = 2. Left subtree's root 2; m = 1; left range [0..0] (4), right [2..2] (5).
  3. pre[2] = 4. 2's left child 4; m = 0; both ranges empty, both calls return null: a leaf.
  4. pre[3] = 5. 2's right child 5; m = 2; a leaf. 2's subtree is done: 1.left = 2.
  5. pre[4] = 3. Right subtree's root 3; m = 4; left range [4..3] empty, so no left child; right range [5..5].
  6. pre[5] = 6. 3's right child 6; m = 5; a leaf. 1.right = 3.
  7. Result. 1(2(4, 5), 3(—, 6)). Check: preorder 1 2 4 5 3 6, inorder 4 2 5 1 3 6.
pre102142533465pin402152133465[0..5]1
pre102142533465pin402152133465[4..5]12453
pre102142533465pin402152133465[0..5]124536
Figure 9.43: Three moments of build. Top: root 1 created and in split around it. Middle: left subtree (2, 4, 5) built; now in[4..5] for the right subtree's root 3. Bottom: done.

The linear search for m, one per node over its range, sets the cost: O(n log n) for a balanced tree, whose ranges halve per level, and O(n2) for a skewed one, whose ranges shrink by one. Storing each inorder value's index in a HashMap beforehand finds m in O(1), bringing the build down to O(n).

Common mistake

Building the left subtree first from postorder. Read backward, postorder gives the root, then the root of the right subtree, so read it from the end and build the right subtree first; otherwise right-subtree elements land in the left.

9.6.9The cost of traversals

Every traversal visits each node once with constant work, so all take O(n) time. They differ in extra memory.

Table 9.26: The cost of the traversal methods.
MethodTimeExtra memoryWhy?
Recursive preorder / inorder / postorderO(n)O(h)At most h + 2 frames on the call stack (including the null call)
Preorder / inorder / postorder with a stackO(n)O(h)The explicit stack takes the place of recursion
Level order with a queueO(n)O(w)w: number of nodes on the widest level

Depth-first memory follows height: small for a balanced tree (h ≈ log2 n), O(n) for a skewed one (h = n − 1), where recursion may hit the stack limit and throw StackOverflowError while the stack-based version, using heap memory, keeps going.

Level-order memory follows width. A perfect tree's last level of about n/2 nodes costs the queue O(n); a skewed tree never queues more than one or two. So the families are opposites: level order is frugal on deep, narrow trees, depth-first on wide, shallow ones — on a perfect tree, O(n) versus O(log n) for recursive inorder.

9.7Common mistakes

Most tree bugs fall into a few types. The table gathers the mistakes discussed throughout; use it as a checklist when reviewing tree code.

Table 9.27: Common mistakes with trees.
MistakeWhat happensThe fix
Forgetting the base caseWithout a node == null check before accessing node.left, a NullPointerException.Every recursive tree method starts with the empty-tree case.
Mixing height conventionsEvery value is off by one.In edges: leaf 0, empty tree −1. In nodes: leaf 1, empty tree 0. Pick one and use it consistently.
Treating full and complete as the sameWrong classification.Full: 0 or 2 children at every node. Complete: all levels but the last filled, the last filling from the left. Neither implies the other.
Building a tree from one traversalSeveral trees give the same output.Inorder plus preorder or postorder is needed. Preorder plus postorder is ambiguous when nodes have one child.
Mixing up the index formulasWrong child or parent; the root may point to itself.1-based: 2i, 2i + 1, ⌊i/2⌋. 0-based: 2i + 1, 2i + 2, ⌊(i − 1)/2⌋.
Recursion on a deep treeIn a skewed tree h = n − 1; hundreds of thousands of frames cause StackOverflowError.A stack- or queue-based version.
Push order in stack-based preorderPushing left first gives root, right, left.Push right first, then left.
Wrong base valueReturning 0 as the maximum of an empty tree gives wrong results for negative values.The base value must be the identity of the combining operation (Integer.MIN_VALUE, 0, 1).

These mistakes share a common root: failing to mirror the tree's recursive structure in the code. A tree is either empty or a root with two subtrees, and a correct tree method handles both cases explicitly — returning a meaningful value for the empty tree, and for a nonempty one combining the subtrees' results with attention to where the root goes. That last point is also the only difference among the traversals: does the root come before, between, or after its subtrees?

9.8Summary

  • A tree is a hierarchical structure of nodes and of edges joining them in parent–child relationships. There is a single root, and every other node has exactly one parent. Hence there are no cycles, a tree of n nodes has n − 1 edges, and there is exactly one path between any two nodes.
  • An internal node has at least one child; a leaf has none. Parent, child, sibling, ancestor, and descendant are used as in a family tree; being on the same level does not make two nodes siblings.
  • Degree is the number of children. Depth is the number of edges from the root to a node, height the number from a node to its farthest leaf. The height of a tree is that of its root, the largest depth. Sources that count nodes give every value one larger.
  • In a binary tree every node has at most two children, and left is distinguished from right. Full: 0 or 2 children at every node. Complete: all levels but the last filled, the last filling from the left. Perfect: all leaves at the same depth; both full and complete.
  • A binary tree has at most 2d nodes at depth d and at most 2h+1 − 1 nodes for height h; ⌊log2 n⌋ ≤ h ≤ n − 1. A full binary tree has one more leaf than internal nodes.
  • In the array representation, the 1-based index i has children 2i and 2i + 1 and parent ⌊i/2⌋; it is ideal for complete trees (heaps) and wastes up to 2n − 1 cells on skewed ones. In the linked representation every node carries left and right references, n + 1 of which are null. General trees are stored with child lists or in first-child – next-sibling form.
  • Preorder visits root-left-right, inorder left-root-right, postorder left-right-root; all three are different moments of the same Euler tour. Level order scans the tree level by level with a queue.
  • Recursive traversals use at most h + 2 frames on the call stack. The explicit-stack versions do the same work in heap memory: preorder pushes right before left; inorder goes left and prints as it pops; postorder uses peek to check whether the right subtree is finished.
  • On an expression tree, preorder gives prefix, postorder postfix, and parenthesized inorder infix notation; the value is computed in postorder. size, height, countLeaves, and findMax follow one pattern: a base value for the empty tree, then a combination of the subtrees' results.
  • Inorder together with preorder (or postorder) determines a binary tree with distinct values uniquely. All traversals take O(n) time; extra memory is O(h) for the depth-first traversals and O(w) for level order.
Table 9.28: Chapter summary.
TopicKey points
Termsroot, parent/child, leaf, internal node; n nodes → n − 1 edges
Measuresdegree = number of children; depth = edges from the root; height = longest downward path
Binary treefull: 0/2 children · complete: last level fills from the left · perfect: every level filled
Representationarray: index relations · linked: left/right references
Traversalpreorder root-L-R · inorder L-root-R · postorder L-R-root · level order with a queue
Costall traversals O(n); extra memory O(h) (stack) or O(w) (queue)

9.9Key terms

Each term links to the course glossary, which gives its Turkish equivalent and its use in other chapters.

9.10Test Yourself

  1. In a general tree, A is the root; A's children are B and C, B's children are D, E, F, C's child is G, and G's children are H and I. Which nodes are leaves and which internal? What are the degree, the number of edges, and the height of the tree? What are the depth and height of G?

    Answer

    The leaves are D, E, F, H, I and the internal nodes A, B, C, G (5 + 4 = 9 nodes). B has the largest degree (3), so the degree of the tree is 3. There are 9 − 1 = 8 edges. The longest path is A→C→G→H, so the tree has height 3. G has depth 2 (A→C→G) and height 1 (G→H).

  2. What are the smallest and largest possible numbers of nodes in a binary tree of height 5? What is the smallest possible height of a binary tree with 100 nodes?

    Answer

    At least h + 1 = 6 (a chain) and at most 26 − 1 = 63 (a perfect tree). For 100 nodes, height 5 is not enough (at most 63 nodes) but 6 is (at most 127); the smallest height is ⌊log2 100⌋ = 6.

  3. How many leaves and internal nodes does a full binary tree with 21 nodes have? Stored in the linked representation, how many null child references does it have?

    Answer

    From n = 2i + 1, i = 10 and l = i + 1 = 11. The number of null references is n + 1 = 22. Check: each leaf has two null references, 2 · 11 = 22, and since both children of every internal node are present, there are no others.

  4. In the 0-based array representation, what are the indices of the parent and children of the node at index 9? At which index does the same node sit in a 1-based array, and what are the corresponding indices there?

    Answer

    0-based: parent ⌊(9 − 1)/2⌋ = 4, left child 2 · 9 + 1 = 19, right child 20. In a 1-based array the same node is at index 10: parent ⌊10/2⌋ = 5, children 20 and 21. Every 1-based index is exactly one more than its 0-based counterpart.

  5. The root is 10; 10's children are 5 and 15; 5's children are 2 and 7; 7 has only a left child, 6; 15 has only a right child, 20. What are the preorder, inorder, postorder, and level-order outputs? Is this tree a binary search tree?

    Answer

    Preorder: 10 5 2 7 6 15 20. Inorder: 2 5 6 7 10 15 20. Postorder: 2 6 7 5 20 15 10. Level order: 10 5 15 2 7 20 6. The inorder output is in increasing order, so the tree is a binary search tree.

  6. preorderIterative runs on the tree of the previous question. What is on the stack (bottom to top) at the end of each pass? What is the most elements the stack holds?

    Answer

    Initially [10]. Pop 10, push 15 and 5: [15, 5]. Pop 5, push 7 and 2: [15, 7, 2]. Pop 2: [15, 7]. Pop 7, push 6: [15, 6]. Pop 6: [15]. Pop 15, push 20: [20]. Pop 20: []. The stack holds at most 3 elements, and the output is in preorder.

  7. Which operator is at the root of the tree for ((9 − 3) / (1 + 2)) × 4? What are its prefix and postfix forms, and what value does evaluate return?

    Answer

    The last operation performed is the multiplication, so the root is ×; the root of its left subtree is / and its right child is 4. Prefix: × / − 9 3 + 1 2 4. Postfix: 9 3 − 1 2 + / 4 ×. evaluate computes 9 − 3 = 6, then 1 + 2 = 3, 6 / 3 = 2, and finally 2 × 4 = 8, returning 8.

  8. Which tree has inorder output 8 4 9 2 5 1 3 7 and postorder output 8 9 4 5 2 7 3 1? What is its preorder output?

    Answer

    The last element of the postorder output is the root: 1. In the inorder output, 1 has 8 4 9 2 5 on its left and 3 7 on its right. In the right subtree, the next-to-last postorder element, 3, is the root, with 7 to its right. In the left subtree the root is 2, with 8 4 9 on its left and 5 on its right. The root of 8 4 9 is 4, with 8 as left and 9 as right child. The tree is 1(2(4(8, 9), 5), 3(—, 7)). Preorder: 1 2 4 8 9 5 3 7.

  9. How many different binary trees have preorder output A B and postorder output B A? What additional information determines the tree?

    Answer

    Two: B can be either the left or the right child of A, and both trees have the same preorder and postorder outputs. The inorder output tells them apart: B A if B is the left child, A B if it is the right. The ambiguity arises because A has only one child.

  10. Suppose the base case of height is written return 0 instead of return -1. What does the method return for the four-node tree rooted at 9 (9's children are 2 and 3; 2's left child is 4)? For a leaf?

    Answer

    A leaf returns 1 + max(0, 0) = 1. Node 2 returns 1 + max(1, 0) = 2, node 3 returns 1, and the root 1 + max(2, 1) = 3. The method now measures height in nodes: every value is one more than under the edge-based definition (the correct answer is 2).

9.11Further reading

  • M. T. Goodrich, R. Tamassia, and M. H. Goldwasser, Data Structures and Algorithms in Java (6th ed., 2014), Chapter 8: defines the general and binary tree ADTs through the position abstraction and gives linked and array implementations; covers traversals, the Euler tour, and expression trees with Java code close to that of these notes.
  • D. E. Knuth, The Art of Computer Programming, Vol. 1: Fundamental Algorithms (3rd ed., 1997), Section 2.3: the classic treatment of tree traversal, covering nonrecursive traversal algorithms, threaded binary trees, and the representation of general trees by binary trees in detail.
  • T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms (4th ed., 2022), the section on representing rooted trees in Chapter 10 and Appendix B.5: the first-child – next-sibling representation and the definitions and counting results for trees, in a more formal style.

9.12References

  1. T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, 4th ed., MIT Press, 2022.
  2. M. T. Goodrich, R. Tamassia, M. H. Goldwasser, Data Structures and Algorithms in Java, 6th ed., Wiley, 2014.
  3. D. E. Knuth, The Art of Computer Programming, Vol. 1: Fundamental Algorithms, 3rd ed., Addison-Wesley, 1997.
  4. Oracle, Java SE 17 API Specification: documentation for java.util.Deque, java.util.ArrayDeque, java.util.Queue, and java.util.LinkedList.
  5. R. Sedgewick, K. Wayne, Algorithms, 4th ed., Addison-Wesley, 2011.

Related pages: Chapter 9 slides · Study questions · Glossary · © Sercan Külcü. All rights reserved.