Chapter 9: Trees

Data Structures
Tree terminologyBinary treesFull · complete · perfect Array and linked representationsTraversalsRecursion and stacksExpression trees
Sercan KÜLCÜ · Giresun University · Computer Engineering

Introduction

Learning Outcomes

Basic concepts

Explain the terms root, parent, child, leaf, depth, and height.

Representations

Represent trees with linked nodes and with arrays.

Traversals

Carry out preorder, inorder, postorder, and level-order traversals, and trace recursion on the call stack.

Uses and costs

Compare general and binary trees by their applications and the cost of their operations.

The Tree Concept

Trees

  • A nonlinear, hierarchical data structure.
  • Made of nodes and the edges that connect them.
  • A single root sits at the top; trees are drawn upside down, growing downward from the root.
  • Every node except the root has exactly one parent, so there are no cycles.
  • A node may have zero or more children.
rootleavesedge
Rule: a tree with n nodes always has n − 1 edges (each node except the root is joined to its parent by exactly one edge).

The Tree Concept

Trees in Everyday Life: Hierarchies

/etcbinhomevarhostsnetworkannalicenotes.txthwphoto.jpg
File systemRoot directory, folders (internal nodes), files (leaves).
BookBook → parts → chapters → sections.
GeographyContinent → country → state/province → city.
Web pageAn HTML document is a tree of nested elements (the DOM).

The Tree Concept

Is It a Tree?

AHGFBCEDJIKPLMNO

Is this structure a tree? It has 16 nodes.

AHGFBCEDJIKPLMNOIDJKMEPNOHLAGBFC

Yes. It is connected and acyclic, with 15 edges for 16 nodes. Any node (say, I) can be chosen as the root and the structure drawn as a tree.

cycletwo parentsdisconnected

Not trees: structures with a cycle, a node with two parents, or disconnected pieces.

The Tree Concept

Basic Parts: Root, Internal Node, Leaf

Root nodeThe topmost node; it has no parent.file system: root directory /
Internal nodeA node with at least one child.file system: folders
LeafA node with no children; also called an external node.file system: files
12934567rootinternal nodeleaves: 9, 4, 5, 6, 7

The root is an internal node too (if it has children). In a one-node tree the root is also a leaf.

The Tree Concept

Tree Relationships

TermMeaning (in the tree on the right)
ParentThe node directly above: D is H's parent
ChildA node directly below: H, I, J → children of D
SiblingNodes with the same parent: H, I, J
AncestorNodes on the path to the root: M's ancestors are H, D, A
DescendantNodes in the subtree: D's descendants are H, I, J, M
ABCDEFGHIJKLM
Careful: being on the same level does not make nodes siblings. F and G have the same depth but different parents (B and C), so they are not siblings.

The Tree Concept

The Terms on a Tree

d=0d=1d=2d=3ABCDEFGHIJKLM

Root: the only node with no parent → A.

d=0d=1d=2d=3ABCDEFGHIJKLM

Parent / child: D is the parent of H, I, and J; H, I, J are D's children.

d=0d=1d=2d=3ABCDEFGHIJKLM

Siblings: H, I, J (their parent is D). F and G are not siblings.

d=0d=1d=2d=3ABCDEFGHIJKLM

Leaves: K, L, F, G, M, I, J (nodes without children).

d=0d=1d=2d=3ABCDEFGHIJKLM

Internal nodes: A, B, C, D, E, H (nodes with at least one child).

d=0d=1d=2d=3ABCDEFGHIJKLM

Ancestors: M's ancestors are H, D, A. Conversely, M is a descendant of H, D, and A.

d=0d=1d=2d=3ABCDEFGHIJKLM321321

Degree = number of children: A 3, B 2, C 1, D 3 … The degree of the tree = the largest degree = 3.

d=0d=1d=2d=3ABCDEFGHIJKLM

Depth: the number of edges from the root to the node. E has depth 2 (A→B→E).

d=0d=1d=2d=3ABCDEFGHIJKLM

Height: the number of edges from the root to the farthest leaf = 3 (A→B→E→K).

d=0d=1d=2d=3ABCDEFGHIJKLM

Subtree: D and all its descendants (H, I, J, M) form a tree of their own.

The Tree Concept

Degree, Depth, Height

MeasureDefinitionIn the previous tree
DegreeThe number of children of a node (its number of subtrees)D → 3
DepthThe number of edges on the path from the root to the node; the root has depth 0E → 2
LevelThe set of nodes at the same depthlevel 2: E … J
Height of a nodeThe number of edges from the node down to its farthest leaf; a leaf has height 0D → 2
Height of the treeThe height of the root = the largest depth3
In this course, height and depth are measured in edges. Some books count nodes instead (numbering levels from 1), which makes every value one larger. When comparing sources, check which definition they use.

The Tree Concept

Depth and Height

ABCDEFGHIJK
Height of the tree = height of the root = 3 (the longest path is A→E→J→K). The deepest node, K, also has depth 3.
NodeHeightDepth
A30
B11
C01
D11
E21
F02
G02
H02
I02
J12
K03

The Tree Concept

Paths and Subtrees

  • Path: a sequence of nodes in which consecutive nodes are joined by edges.
    p = (v₀, e₁, v₁, …, ek, vk)
  • start(p) = v₀, end(p) = vk, length(p) = k (the number of edges).
  • In a tree, any two nodes are joined by exactly one simple path; a second path would create a cycle.
  • Subtree: a node together with all its descendants; it is a tree itself. Example: a folder and everything under it.
ABCDEFGHIJKLM
Highlighted path: K → E → B → A → D → H → M, length 6. There is no other path between these two nodes.

The Tree Concept

Basic Kinds of Trees

KindProperty
General treeNo limit on the number of children a node may have.
k-ary treeEach node has at most k children.
Binary treeEach 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 and keys in the right subtree larger.
general treeternary tree (k = 3)binary tree83101614binary search tree

The Tree Concept

The Tree Abstract Data Type (Tree ADT)

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); // a leaf
  boolean isRoot(Position<E> p);     // no parent
  int size();
  boolean isEmpty();
  Iterable<Position<E>> positions(); // all nodes
}
// Position<E>: E getElement();

The binary tree ADT adds left(p), right(p), and sibling(p) (as in Goodrich et al.).

ABCDEFGparent(p)pchildren(p): D, Eleafroot

A position is an abstraction of a "place" that hides the node itself: the user sees only the element and the ADT methods.

The Tree Concept · Step through the code

Code Trace: depth(G) and height(B) with the ADT

ABCDEFGrunning nowon the stack, waitingreturned its valuenot called yet

depth(G): G is not the root → call depth(E) on its parent, E (moving up).

ABCDEFGrunning nowon the stack, waitingreturned its valuenot called yet

depth(E): E is not the root → call depth(B) on its parent, B (moving up).

ABCDEFGrunning nowon the stack, waitingreturned its valuenot called yet

depth(B): B is not the root → call depth(A) on its parent, A (moving up).

ABCDEFG↑ 0running nowon the stack, waitingreturned its valuenot called yet

depth(A): A is the root → return 0. This is the base case; now the frames start returning.

ABCDEFG↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

depth(B) = 1 + 0 = 1 is returned.

ABCDEFG↑ 0↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

depth(E) = 1 + 1 = 2 is returned.

ABCDEFG↑ 0↑ 1↑ 2↑ 3running nowon the stack, waitingreturned its valuenot called yet

depth(G) = 1 + 2 = 3 is returned. G has depth 3: 3 edges from the root to G (A–B–E–G). Cost: O(d + 1).

ABCDEFGrunning nowon the stack, waitingreturned its valuenot called yet

Now height(B): the recursion goes down, through the children (depth looks up, height looks down).

ABCDEFGrunning nowon the stack, waitingreturned its valuenot called yet

height(B): h = 0. Children: D, E. Recurse on the first child, D.

ABCDEFG↑ 0running nowon the stack, waitingreturned its valuenot called yet

height(D): h = 0; D has no children, so the loop never runs → return 0 (a leaf).

ABCDEFG↑ 0running nowon the stack, waitingreturned its valuenot called yet

D returned 0: h = max(0, 1 + 0) = 1. Next child: E.

ABCDEFG↑ 0running nowon the stack, waitingreturned its valuenot called yet

height(E): h = 0. Children: G. Recurse on the first child, G.

ABCDEFG↑ 0↑ 0running nowon the stack, waitingreturned its valuenot called yet

height(G): h = 0; G has no children, so the loop never runs → return 0 (a leaf).

ABCDEFG↑ 0↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

G returned 0: h = max(0, 1 + 0) = 1. No children left → return 1.

ABCDEFG↑ 0↑ 0↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

E returned 1: h = max(1, 1 + 1) = 2. No children left → return 2. Height(B) = 2 (B–E–G); each node is visited once.

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;
}

Binary Trees

Binary Trees

  • Each node has at most two children: a left child and a right child.
  • Position matters: a tree whose only child is a left child differs from one whose only child is a right child.
  • Recursive definition: a binary tree is either empty or consists of a root and two binary trees (the left and right subtrees).
ABAB≠
ABCDABCDEFGHIJK

The chain on the left is a valid binary tree too (a skewed tree): nodes need not be spread out evenly.

Binary Trees

Full, Complete, and Perfect

Full binary treeEvery node has either 0 or 2 children; no node has just one.
Complete binary treeEvery level except the last is full, and the last level fills from left to right with no gaps.
Perfect binary treeEvery internal node has 2 children and all leaves have the same depth.
A perfect tree is both full and complete; with height h it has 2h+1 − 1 nodes. Books do not all use "full" and "complete" the same way, so check the definition before you rely on one.

Binary Trees

Which One? Full, Complete, or Perfect?

ABCDEHI
Fullyes
Completeno
Perfectno

Every node has 0 or 2 children → full. But the bottom level hangs under E while the space under C is empty → not complete.

12345678910
Fullno
Completeyes
Perfectno

The last level (8, 9, 10) fills from left to right with no gaps → complete. Node 5 has only one child (10) → not full.

12345678911
Fullno
Completeno
Perfectno

Node 5 has one child → not full. Position 10 is empty while 11 is filled → not complete. Neither complete nor full.

1234567
Fullyes
Completeyes
Perfectyes

Every internal node has 2 children and all leaves have the same depth → perfect (and therefore full and complete).

Binary Trees

Properties of Binary Trees

PropertyFormulaExample
Most nodes at depth d2dd = 3 → at most 8 nodes
Most nodes in a tree of height h2h+1 − 1h = 3 → 15 (perfect tree)
Fewest nodes in a tree of height hh + 1skewed (chain) tree
Height of a tree with n nodes⌊log2 n⌋ ≤ h ≤ n − 1n = 15 → 3 ≤ h ≤ 14
Number of edges in a tree with n nodesn − 1each node has one parent
Number of leaves in a full binary treeinternal nodes + 17 nodes → 3 internal, 4 leaves
Height sets the cost of tree operations: in a balanced tree h ≈ log2 n (1 million nodes → ≈ 20 levels); in a skewed tree h = n − 1.

Binary Trees · Step by step

Full Binary Trees: Why Leaves = Internal Nodes + 1?

Linternal i = 0leaves l = 1l − i = 1

A one-node tree: the root is a leaf (L). i = 0, l = 1 → l = i + 1.

ILLinternal i = 1leaves l = 2l − i = 1

Give a leaf two children (a full tree allows 0 or 2): the leaf becomes an internal node (i + 1) and two new leaves appear (l − 1 + 2 = l + 1). The difference stays the same.

ILILLinternal i = 2leaves l = 3l − i = 1

Give a leaf two children (a full tree allows 0 or 2): the leaf becomes an internal node (i + 1) and two new leaves appear (l − 1 + 2 = l + 1). The difference stays the same.

ILLILLIinternal i = 3leaves l = 4l − i = 1

Give a leaf two children (a full tree allows 0 or 2): the leaf becomes an internal node (i + 1) and two new leaves appear (l − 1 + 2 = l + 1). The difference stays the same.

LLIILLILIinternal i = 4leaves l = 5l − i = 1

Each expansion adds 1 to both i and l; since l − i = 1 at the start, every full binary tree has l = i + 1 (by induction). Consequently n = 2i + 1, so n is always odd.

I: internal node, L: leaf; orange: the leaves added in the last step. This property is used in expression trees (every operator has 2 operands) and Huffman trees.

Representations

Array Representation

Nodes are placed in the array level by level, left to right. The root goes at index 1 in a 1-based array, at index 0 in a 0-based one. For the node at index i:

1-based0-based
Left child2i2i + 1
Right child2i + 12i + 2
Parent⌊i / 2⌋⌊(i − 1) / 2⌋

Example: E (i = 5) → children at 10 and 11 (empty), parent at ⌊5/2⌋ = 2 (B).

ABCDEFGHI1234567890A1B2C3D4E5F6G7H8I9

No references are stored; parent and child are reached with O(1) arithmetic. Ideal for complete binary trees (such as heaps).

Representations · Step through the code

Code Trace: Index Arithmetic in the Array Representation

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

left(2) = 2·2 = 4 → a[4] = D, B's left child (the array is 1-based; a[0] is empty). No references: positions come from arithmetic.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

right(2) = 2·2 + 1 = 5 → a[5] = E. The right child always sits right next to the left child.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

parent(2) = 2 / 2 = 1 → a[1] = A (the root).

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

D (i = 4): children 8 → H and 9 → I.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

E (i = 5): 10 and 11 > n = 9 → outside the array, so E has no children (a leaf). The check is 2i ≤ n.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

parent(9) = 9 / 2 = 4: integer division rounds down (⌊4.5⌋ = 4) → D. Since 8 and 9 are siblings, both give 4.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

pathToRoot(a, 9): follow the chain of parents from I up to the root.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

9 ≥ 1 → the loop continues; a[9] = I is printed.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

i = parent(9) = 4 → D.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

4 ≥ 1 → the loop continues; a[4] = D is printed.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

i = parent(4) = 2 → B.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

2 ≥ 1 → the loop continues; a[2] = B is printed.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

i = parent(2) = 1 → A.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

1 ≥ 1 → the loop continues; a[1] = A is printed.

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

i = parent(1) = 0 (the root has no parent).

ABCDEFGHI123456789array a (n = 9)0A1B2C3D4E5F6G7H8I91011none (> n)

0 ≥ 1 is false → the loop ends. Output: I D B A. Each step halves the index → O(log n) (as many steps as the depth).

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);
  }
}

Representations

Wasted Space in Arrays

ABCDE124816
  • In a skewed tree the indices grow exponentially: 1, 2, 4, 8, 16.
  • 5 nodes need 16 cells, leaving 11 empty. Skewed to the right, the indices would be 1, 3, 7, 15, 31 → 31 cells.
  • In general, a tree of n nodes may need up to 2n − 1 cells.
A1B23C4567D89101112131415E16

Array representation

  • No references, fast index arithmetic
  • No gaps for complete trees

But

  • Large waste for sparse or skewed trees
  • Fixed size; growing means copying

Representations

Linked Representation

  • Each tree node is an object: data plus references to its left and right children.
  • A missing child is a null reference.
  • The size is dynamic; memory is allocated only for nodes that exist.
  • Reaching a node means starting at the root and following references.
leftdatarightleft subtreeright subtree
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
    }
}
// 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);

Representations

Linked Representation: Example

ABCDEFGcrossed cell = null reference
7 nodes14 reference fields: 6 in use (the n − 1 edges), 8 null.
General ruleA binary tree with n nodes always has n + 1 null references.
Going upIf needed, a node can also carry a parent reference.

Representations

Array or Linked?

CriterionArray representationLinked representation
MemoryMinimal for complete trees; up to 2n − 1 cells for sparse ones2 extra references per node; no waste
Child / parent accessO(1) by index arithmeticChild O(1); the parent needs an extra reference
k-th node on level iO(1): index 2i + k − 1 (1-based, k ≥ 1)Requires walking down from the root
GrowthWhen the array fills up, it is grown by copyingAdding a node is O(1) (if its position is known)
Best suited forComplete binary trees (heaps)General trees whose shape changes (BSTs, expression trees)
Rule of thumb: if the tree stays complete, use an array; if its shape is unpredictable, use links.

Representations

Representing General Trees

When a node can have any number of children, two approaches 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 second approach every node again holds two references: first child (down) and next sibling (across). This lets any general tree be stored like a binary tree.

ABCDEFGABCDEFG

Left: a general tree; right: its first child (solid arrow) – next sibling (dashed arrow) representation.

Traversals

Tree Traversals

Traversal: visiting every node exactly once. Depth-first (DFS) traversals differ in when they visit the root; level-order (BFS) proceeds level by level.
1234567
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/height
Level orderlevel by level, left to right1 2 3 4 5 6 7Level-based operations

Traversals

Preorder Traversal

  1. Visit the root.
  2. Traverse the left subtree in preorder.
  3. Traverse the right subtree in preorder.

Use: copying a tree, writing the nodes to an array, prefix expressions.

visit(root);
preorder(root.left);
preorder(root.right);
1234567Output: 1currentvisitedwaiting

Visit the root, 1; then move to the left subtree.

1234567Output: 1 2currentvisitedwaiting

Visit 2; then its left subtree.

1234567Output: 1 2 4currentvisitedwaiting

4 is a leaf: visit it; it has no children.

1234567Output: 1 2 4 5currentvisitedwaiting

2's right subtree: 5.

1234567Output: 1 2 4 5 3currentvisitedwaiting

The left subtree is done; 1's right subtree: 3.

1234567Output: 1 2 4 5 3 6currentvisitedwaiting

3's left child is 6.

1234567Output: 1 2 4 5 3 6 7currentvisitedwaiting

3's right child is 7. Output: 1 2 4 5 3 6 7

Traversals

Inorder Traversal

  1. Traverse the left subtree in inorder.
  2. Visit the root.
  3. Traverse the right subtree in inorder.

Use: listing the keys of a binary search tree in increasing order, infix expressions.

inorder(root.left);
visit(root);
inorder(root.right);
1234567Output: 4currentvisitedwaiting

Keep going left from the root; the first node visited is the leftmost one: 4.

1234567Output: 4 2currentvisitedwaiting

4's subtree is done → its parent, 2.

1234567Output: 4 2 5currentvisitedwaiting

2's right subtree: 5.

1234567Output: 4 2 5 1currentvisitedwaiting

The left subtree is complete → the root, 1.

1234567Output: 4 2 5 1 6currentvisitedwaiting

In the right subtree, again go all the way left: 6.

1234567Output: 4 2 5 1 6 3currentvisitedwaiting

After 6, its parent, 3.

1234567Output: 4 2 5 1 6 3 7currentvisitedwaiting

Finally 7. Output: 4 2 5 1 6 3 7

Traversals

Postorder Traversal

  1. Traverse the left subtree in postorder.
  2. Traverse the right subtree in postorder.
  3. Visit the root.

Use: deleting a tree (children first), computing size/height, postfix expressions.

postorder(root.left);
postorder(root.right);
visit(root);
1234567Output: 4currentvisitedwaiting

The leftmost leaf, 4, is visited first.

1234567Output: 4 5currentvisitedwaiting

Then 2's right child, 5.

1234567Output: 4 5 2currentvisitedwaiting

Both of 2's subtrees are done → 2.

1234567Output: 4 5 2 6currentvisitedwaiting

1's right subtree: 6 first.

1234567Output: 4 5 2 6 7currentvisitedwaiting

Then 7.

1234567Output: 4 5 2 6 7 3currentvisitedwaiting

3's children are done → 3.

1234567Output: 4 5 2 6 7 3 1currentvisitedwaiting

The root is visited last: 1. Output: 4 5 2 6 7 3 1

Traversals · Step by step

Three Traversals, One Walk: the Euler Tour

ABCDE

Walk around the tree, starting to the left of the root and keeping your left hand on the edges (an Euler tour). You pass each node three times: on its left, below it, and on its right.

ABCDE12345Left mark → Preorder:A B D E C

Write a node down when you pass on its left (the first encounter) and you get preorder: A B D E C.

ABCDE1234512345Left mark → Preorder:A B D E CBottom mark → Inorder:D B E A C

Write it down when you pass below it (left subtree done, right not yet started) and you get inorder: D B E A C.

ABCDE123451234512345Left mark → Preorder:A B D E CBottom mark → Inorder:D B E A CRight mark → Postorder:D E B C A

Write it down when you pass on its right (both subtrees done) and you get postorder: D E B C A. The three traversals are different moments of the same walk, which takes O(n).

Traversals

Level-Order Traversal

  • Starts at the root, scans each level from left to right, then moves down a level.
  • It is breadth-first search (BFS) applied to a tree.
  • Uses a queue (FIFO) instead of recursion: the children of the node that arrived first are processed first.

Use: level-based operations (the sum of each level, the nearest leaf), printing a tree level by level.

level 0level 1level 21234567Output: 1 2 3 4 5 6 7

Traversals · Step through the code

Recursive Preorder and the Call Stack

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(9) is called (the root). Not null → print the root, 9, then the subtrees.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(2) is called (left child of 9). Not null → print the root, 2, then the subtrees.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(4) is called (left child of 2). Not null → print the root, 4, then the subtrees.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(4.left) = preorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(4.right) = preorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(4) returns (line 8); its frame is popped and we are back in preorder(2).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(2.right) = preorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(2) returns (line 8); its frame is popped and we are back in preorder(9).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(3) is called (right child of 9). Not null → print the root, 3, then the subtrees.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(3.left) = preorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(3.right) = preorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(3) returns (line 8); its frame is popped and we are back in preorder(9).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

preorder(9) returns (line 8); the stack is empty. Output: 9 2 4 3 — each node once, at most h + 2 = 4 frames.

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

Traversals · Step through the code

Recursive Inorder and the Call Stack

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(9) is called (the root). 9 is not printed yet: the left subtree comes first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(2) is called (left child of 9). 2 is not printed yet: the left subtree comes first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(4) is called (left child of 2). 4 is not printed yet: the left subtree comes first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(4.left) = inorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

Left subtree done; back in inorder(4) → print 4. The right subtree is next.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(4.right) = inorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(4) returns (line 8); its frame is popped and we are back in inorder(2).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

Left subtree done; back in inorder(2) → print 2. The right subtree is next.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(2.right) = inorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(2) returns (line 8); its frame is popped and we are back in inorder(9).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

Left subtree done; back in inorder(9) → print 9. The right subtree is next.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(3) is called (right child of 9). 3 is not printed yet: the left subtree comes first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(3.left) = inorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

Left subtree done; back in inorder(3) → print 3. The right subtree is next.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(3.right) = inorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(3) returns (line 8); its frame is popped and we are back in inorder(9).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

inorder(9) returns (line 8); the stack is empty. Output: 4 2 9 3 — each node once, at most h + 2 = 4 frames.

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

Traversals · Step through the code

Recursive Postorder and the Call Stack

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(9) is called (the root). The root waits until last: left subtree first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(2) is called (left child of 9). The root waits until last: left subtree first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(4) is called (left child of 2). The root waits until last: left subtree first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(4.left) = postorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(4.right) = postorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(4): both subtrees are done → print 4 now.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(4) returns (line 8); its frame is popped and we are back in postorder(2).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(2.right) = postorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(2): both subtrees are done → print 2 now.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(2) returns (line 8); its frame is popped and we are back in postorder(9).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(3) is called (right child of 9). The root waits until last: left subtree first.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(3.left) = postorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(3.right) = postorder(null): base case; return at once, since an empty subtree needs no work.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(3): both subtrees are done → print 3 now.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(3) returns (line 8); its frame is popped and we are back in postorder(9).

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(9): both subtrees are done → print 9 now.

nullnullnullnullnull9234running nowon the call stackprintednot printed yet

postorder(9) returns (line 8); the stack is empty. Output: 4 2 3 9 — each node once, at most h + 2 = 4 frames.

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

Traversals · Step through the code

Preorder Traversal with a Stack

1234567Stack1topcurrenton the stackprinted

Push the root. The stack stands in for the call stack of the recursive version.

1234567Stackemptycurrenton the stackprinted

pop() returns 1, printed at once (preorder: root first).

1234567Stack3topcurrenton the stackprinted

Push the right child, 3: right goes first, so left ends up on top and is processed first (LIFO).

1234567Stack32topcurrenton the stackprinted

Push the left child, 2. It is now on top, so the next pop() returns it.

1234567Stack3topcurrenton the stackprinted

pop() returns 2, printed at once (preorder: root first).

1234567Stack35topcurrenton the stackprinted

Push the right child, 5: right goes first, so left ends up on top and is processed first (LIFO).

1234567Stack354topcurrenton the stackprinted

Push the left child, 4. It is now on top, so the next pop() returns it.

1234567Stack35topcurrenton the stackprinted

pop() returns 4, printed at once (preorder: root first). 4 is a leaf: no children to push.

1234567Stack3topcurrenton the stackprinted

pop() returns 5, printed at once (preorder: root first). 5 is a leaf: no children to push.

1234567Stackemptycurrenton the stackprinted

pop() returns 3, printed at once (preorder: root first).

1234567Stack7topcurrenton the stackprinted

Push the right child, 7: right goes first, so left ends up on top and is processed first (LIFO).

1234567Stack76topcurrenton the stackprinted

Push the left child, 6. It is now on top, so the next pop() returns it.

1234567Stack7topcurrenton the stackprinted

pop() returns 6, printed at once (preorder: root first). 6 is a leaf: no children to push.

1234567Stackemptycurrenton the stackprinted

pop() returns 7, printed at once (preorder: root first). 7 is a leaf: no children to push.

1234567Stackemptycurrenton the stackprinted

The stack is empty → the loop ends. Output: 1 2 4 5 3 6 7 — the same as recursive preorder.

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
  }
}

Traversals · Step through the code

Inorder Traversal with a Stack

123456Stackemptycurrenton the stackprinted

temp starts at the root; the stack is empty.

123456Stack1topcurrenton the stackprinted

Push 1 (to be printed later); temp = 1.left = 2. Keep going left.

123456Stack12topcurrenton the stackprinted

Push 2 (to be printed later); temp = 2.left = 4. Keep going left.

123456Stack124topcurrenton the stackprinted

Push 4 (to be printed later); temp = 4.left = null. Nothing more to the left.

123456Stack12topcurrenton the stackprinted

temp is null → pop 4 and print it (everything to its left is done). temp = 4.right = null.

123456Stack1topcurrenton the stackprinted

temp is null → pop 2 and print it (everything to its left is done). temp = 2.right = 5.

123456Stack15topcurrenton the stackprinted

Push 5 (to be printed later); temp = 5.left = null. Nothing more to the left.

123456Stack1topcurrenton the stackprinted

temp is null → pop 5 and print it (everything to its left is done). temp = 5.right = null.

123456Stackemptycurrenton the stackprinted

temp is null → pop 1 and print it (everything to its left is done). temp = 1.right = 3.

123456Stack3topcurrenton the stackprinted

Push 3 (to be printed later); temp = 3.left = 6. Keep going left.

123456Stack36topcurrenton the stackprinted

Push 6 (to be printed later); temp = 6.left = null. Nothing more to the left.

123456Stack3topcurrenton the stackprinted

temp is null → pop 6 and print it (everything to its left is done). temp = 6.right = null.

123456Stackemptycurrenton the stackprinted

temp is null → pop 3 and print it (everything to its left is done). temp = 3.right = null.

123456Stackemptycurrenton the stackprinted

The stack is empty and temp is null → the loop ends. Output: 4 2 5 1 6 3 (left → root → right).

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;
    }
  }
}

Traversals

Postorder Traversal with a Stack: Code

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;
      }
    }
  }
}
  • A node must be printed only after both of its subtrees, so the node on the stack is not popped right away: we first look at it with peek.
  • If the top node has a right child, we go into that subtree first.
  • If it has no right child, the node is printed. If the node just printed is the right child of the node now on top, that parent is finished too → it is popped and printed as well.

Traversals · Step through the code

Postorder Traversal with a Stack: Trace

123456Stackemptycurrenton the stackprinted

current starts at the root; the stack is empty.

123456Stack1topcurrenton the stackprinted

Push 1; current = 1.left = 2.

123456Stack12topcurrenton the stackprinted

Push 2; current = 2.left = 4.

123456Stack124topcurrenton the stackprinted

Push 4; current = 4.left = null.

123456Stack12topcurrenton the stackprinted

peek() = 4 has no right child, so both its subtrees are done: pop and print 4.

123456Stack12topcurrenton the stackprinted

4 is not the right child of 2 on top (2.right = 5) → the inner loop stops; 2's right subtree has not been entered yet.

123456Stack12topcurrenton the stackprinted

peek() = 2 has a right child, 5, so 2 cannot be printed yet; current = 5 moves into the right subtree first.

123456Stack125topcurrenton the stackprinted

Push 5; current = 5.left = null.

123456Stack12topcurrenton the stackprinted

peek() = 5 has no right child, so both its subtrees are done: pop and print 5.

123456Stack1topcurrenton the stackprinted

The node just printed was the right child of 2, so its right subtree is done too: pop and print 2.

123456Stack1topcurrenton the stackprinted

2 is not the right child of 1 on top (1.right = 3) → the inner loop stops; 1's right subtree has not been entered yet.

123456Stack1topcurrenton the stackprinted

peek() = 1 has a right child, 3, so 1 cannot be printed yet; current = 3 moves into the right subtree first.

123456Stack13topcurrenton the stackprinted

Push 3; current = 3.left = null.

123456Stack13topcurrenton the stackprinted

peek() = 3 has a right child, 6, so 3 cannot be printed yet; current = 6 moves into the right subtree first.

123456Stack136topcurrenton the stackprinted

Push 6; current = 6.left = null.

123456Stack13topcurrenton the stackprinted

peek() = 6 has no right child, so both its subtrees are done: pop and print 6.

123456Stack1topcurrenton the stackprinted

The node just printed was the right child of 3, so its right subtree is done too: pop and print 3.

123456Stackemptycurrenton the stackprinted

The node just printed was the right child of 1, so its right subtree is done too: pop and print 1.

123456Stackemptycurrenton the stackprinted

The stack is empty → the inner loop stops.

123456Stackemptycurrenton the stackprinted

current is null and the stack is empty → done. Output: 4 5 2 6 3 1 (left → right → root).

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;
      }
    }
  }
}

Traversals · Step through the code

Level-Order Traversal with a Queue

43758Queue4frontcurrentin the queueprinted

Add the root to the queue. A queue is FIFO: first in, first out.

43758Queueemptycurrentin the queueprinted

poll() returns 4, and prints it.

43758Queue3frontcurrentin the queueprinted

The left child, 3, joins the back of the queue; it waits behind the nodes ahead of it.

43758Queue37frontrearcurrentin the queueprinted

The right child, 7, joins the back.

43758Queue7frontcurrentin the queueprinted

poll() returns 3, and prints it.

43758Queue75frontrearcurrentin the queueprinted

The left child, 5, joins the back of the queue; it waits behind the nodes ahead of it.

43758Queue758frontrearcurrentin the queueprinted

The right child, 8, joins the back.

43758Queue58frontrearcurrentin the queueprinted

poll() returns 7, and prints it. A leaf: no children to add.

43758Queue8frontcurrentin the queueprinted

poll() returns 5, and prints it. A leaf: no children to add.

43758Queueemptycurrentin the queueprinted

poll() returns 8, and prints it. A leaf: no children to add.

43758Queueemptycurrentin the queueprinted

The queue is empty → the loop ends. Output: 4 3 7 5 8 — level by level (preorder would give 4 3 5 8 7).

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);
  }
}

Traversals

Application: Expression Trees

  • Internal nodes are operators; leaves are operands.
  • The same tree gives different notations depending on the traversal.
  • The value is computed by a postorder traversal: first the values of both subtrees, then the operator.
×+−3452
TraversalOutputNotation
Preorder× + 3 4 − 5 2prefix
Inorder3 + 4 × 5 − 2 → with parentheses: (3 + 4) × (5 − 2)infix
Postorder3 4 + 5 2 − × → 7 × 3 = 21postfix

The inorder output loses the parentheses; to write the expression correctly, wrap each subtree in parentheses.

Traversals · Step through the code

Evaluating an Expression Tree: evaluate()

×+−3452

evaluate(×): an internal node (operator). Before applying it we need the value of the left operand → evaluate(+).

×+−3452

evaluate(+): an internal node (operator). Before applying it we need the value of the left operand → evaluate(3).

×+−3452↑ 3

evaluate(3): no children → a leaf, which returns its number: 3.

×+−3452↑ 3

a = 3. Now the right operand: evaluate(4).

×+−3452↑ 3↑ 4

evaluate(4): no children → a leaf, which returns its number: 4.

×+−3452↑ 3↑ 4↑ 7

b = 4. Both subtrees are ready: 3 + 4 = 7 is returned (postorder logic).

×+−3452↑ 3↑ 4↑ 7

a = 7. Now the right operand: evaluate(−).

×+−3452↑ 3↑ 4↑ 7

evaluate(−): an internal node (operator). Before applying it we need the value of the left operand → evaluate(5).

×+−3452↑ 3↑ 4↑ 7↑ 5

evaluate(5): no children → a leaf, which returns its number: 5.

×+−3452↑ 3↑ 4↑ 7↑ 5

a = 5. Now the right operand: evaluate(2).

×+−3452↑ 3↑ 4↑ 7↑ 5↑ 2

evaluate(2): no children → a leaf, which returns its number: 2.

×+−3452↑ 3↑ 4↑ 7↑ 5↑ 2↑ 3

b = 2. Both subtrees are ready: 5 − 2 = 3 is returned (postorder logic).

×+−3452↑ 3↑ 4↑ 7↑ 5↑ 2↑ 3↑ 21

b = 3. Both subtrees are ready: 7 × 3 = 21 is returned (postorder logic). (3 + 4) × (5 − 2) = 21

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;
  }
}

Traversals · Step through the code

Finding the Maximum in a Binary Tree

nullnullnullnullnullnull43758

findMax(4): result = 4; now find the maximum of the left subtree.

nullnullnullnullnullnull43758

findMax(3): result = 3; now find the maximum of the left subtree.

nullnullnullnullnullnull43758

findMax(5): result = 5; now find the maximum of the left subtree.

nullMINnullnullnullnullnull43758

findMax(5.left): empty subtree → returns Integer.MIN_VALUE, which never wins a comparison.

nullMINnullnullnullnullnull43758

The left result is MIN (left = MIN); now the right subtree.

nullMINnullMINnullnullnullnull43758

findMax(5.right): empty subtree → returns Integer.MIN_VALUE, which never wins a comparison.

nullMINnullMINnullnullnullnull43758↑ 5

Comparisons: left = MIN ≤ 5; right = MIN ≤ 5. return 5.

nullMINnullMINnullnullnullnull43758↑ 5

The left result is 5 (left = 5); now the right subtree.

nullMINnullMINnullnullnullnull43758↑ 5

findMax(8): result = 8; now find the maximum of the left subtree.

nullMINnullMINnullMINnullnullnull43758↑ 5

findMax(8.left): empty subtree → returns Integer.MIN_VALUE, which never wins a comparison.

nullMINnullMINnullMINnullnullnull43758↑ 5

The left result is MIN (left = MIN); now the right subtree.

nullMINnullMINnullMINnullMINnullnull43758↑ 5

findMax(8.right): empty subtree → returns Integer.MIN_VALUE, which never wins a comparison.

nullMINnullMINnullMINnullMINnullnull43758↑ 5↑ 8

Comparisons: left = MIN ≤ 8; right = MIN ≤ 8. return 8.

nullMINnullMINnullMINnullMINnullnull43758↑ 5↑ 8↑ 8

Comparisons: left = 5 > 3 → result = 5; right = 8 > 5 → result = 8. return 8.

nullMINnullMINnullMINnullMINnullnull43758↑ 5↑ 8↑ 8

The left result is 8 (left = 8); now the right subtree.

nullMINnullMINnullMINnullMINnullnull43758↑ 5↑ 8↑ 8

findMax(7): result = 7; now find the maximum of the left subtree.

nullMINnullMINnullMINnullMINnullMINnull43758↑ 5↑ 8↑ 8

findMax(7.left): empty subtree → returns Integer.MIN_VALUE, which never wins a comparison.

nullMINnullMINnullMINnullMINnullMINnull43758↑ 5↑ 8↑ 8

The left result is MIN (left = MIN); now the right subtree.

nullMINnullMINnullMINnullMINnullMINnullMIN43758↑ 5↑ 8↑ 8

findMax(7.right): empty subtree → returns Integer.MIN_VALUE, which never wins a comparison.

nullMINnullMINnullMINnullMINnullMINnullMIN43758↑ 5↑ 8↑ 8↑ 7

Comparisons: left = MIN ≤ 7; right = MIN ≤ 7. return 7.

nullMINnullMINnullMINnullMINnullMINnullMIN43758↑ 5↑ 8↑ 8↑ 7↑ 8

Comparisons: left = 8 > 4 → result = 8; right = 7 ≤ 8. return 8. Result: 8

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;
}

Traversals

Recursive Tree Functions

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);
}
Common patternA base case for the empty tree; otherwise combine the results of the two subtrees (postorder logic).
For the tree 9 → (2 → 4), 3size = 4height = 2countLeaves = 2 (4 and 3)
CostEach node is processed once: O(n) time, O(h) stack space.

Traversals · Step through the code

Code Trace: size() — values travel upward

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

size(9) (the root): not null → first call size(2) on the left subtree.

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

size(2) (left child of 9): not null → first call size(4) on the left subtree.

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

size(4) (left child of 2): not null → first call size(null) on the left subtree.

null0nullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

size(4.left) = size(null): empty tree → return 0.

null0nullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

The left result is 0. Now the right subtree: size(null).

null0null0nullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

size(4.right) = size(null): empty tree → return 0.

null0null0nullnullnull9234↑ 1running nowon the stack, waitingreturned its valuenot called yet

size(4) = 1 + 0 + 0 = 1 is returned to the frame of 2.

null0null0nullnullnull9234↑ 1running nowon the stack, waitingreturned its valuenot called yet

The left result is 1. Now the right subtree: size(null).

null0null0null0nullnull9234↑ 1running nowon the stack, waitingreturned its valuenot called yet

size(2.right) = size(null): empty tree → return 0.

null0null0null0nullnull9234↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

size(2) = 1 + 1 + 0 = 2 is returned to the frame of 9.

null0null0null0nullnull9234↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

The left result is 2. Now the right subtree: size(3).

null0null0null0nullnull9234↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

size(3) (right child of 9): not null → first call size(null) on the left subtree.

null0null0null0null0null9234↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

size(3.left) = size(null): empty tree → return 0.

null0null0null0null0null9234↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

The left result is 0. Now the right subtree: size(null).

null0null0null0null0null09234↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

size(3.right) = size(null): empty tree → return 0.

null0null0null0null0null09234↑ 1↑ 2↑ 1running nowon the stack, waitingreturned its valuenot called yet

size(3) = 1 + 0 + 0 = 1 is returned to the frame of 9.

null0null0null0null0null09234↑ 1↑ 2↑ 1↑ 4running nowon the stack, waitingreturned its valuenot called yet

size(9) = 1 + 2 + 1 = 4 is returned. Result: 4

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

Traversals · Step through the code

Code Trace: height()

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

height(9) (the root): not null → first call height(2) on the left subtree.

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

height(2) (left child of 9): not null → first call height(4) on the left subtree.

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

height(4) (left child of 2): not null → first call height(null) on the left subtree.

null-1nullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

height(4.left) = height(null): empty tree → return -1. The empty tree has height −1 by convention, so a leaf gets 1 + max(−1, −1) = 0.

null-1nullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

The left result is -1. Now the right subtree: height(null).

null-1null-1nullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

height(4.right) = height(null): empty tree → return -1.

null-1null-1nullnullnull9234↑ 0running nowon the stack, waitingreturned its valuenot called yet

height(4) = 1 + max(-1, -1) = 0 is returned to the frame of 2.

null-1null-1nullnullnull9234↑ 0running nowon the stack, waitingreturned its valuenot called yet

The left result is 0. Now the right subtree: height(null).

null-1null-1null-1nullnull9234↑ 0running nowon the stack, waitingreturned its valuenot called yet

height(2.right) = height(null): empty tree → return -1.

null-1null-1null-1nullnull9234↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

height(2) = 1 + max(0, -1) = 1 is returned to the frame of 9.

null-1null-1null-1nullnull9234↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

The left result is 1. Now the right subtree: height(3).

null-1null-1null-1nullnull9234↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

height(3) (right child of 9): not null → first call height(null) on the left subtree.

null-1null-1null-1null-1null9234↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

height(3.left) = height(null): empty tree → return -1.

null-1null-1null-1null-1null9234↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

The left result is -1. Now the right subtree: height(null).

null-1null-1null-1null-1null-19234↑ 0↑ 1running nowon the stack, waitingreturned its valuenot called yet

height(3.right) = height(null): empty tree → return -1.

null-1null-1null-1null-1null-19234↑ 0↑ 1↑ 0running nowon the stack, waitingreturned its valuenot called yet

height(3) = 1 + max(-1, -1) = 0 is returned to the frame of 9.

null-1null-1null-1null-1null-19234↑ 0↑ 1↑ 0↑ 2running nowon the stack, waitingreturned its valuenot called yet

height(9) = 1 + max(1, 0) = 2 is returned. Result: 2

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));
}

Traversals · Step through the code

Code Trace: countLeaves()

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

countLeaves(9) (the root): not a leaf → add up the leaves of both subtrees, left first.

nullnullnullnullnull9234running nowon the stack, waitingreturned its valuenot called yet

countLeaves(2) (left child of 9): not a leaf → add up the leaves of both subtrees, left first.

nullnullnullnullnull9234↑ 1running nowon the stack, waitingreturned its valuenot called yet

countLeaves(4) (left child of 2): both children are null → a leaf; return 1 with no further calls.

nullnullnull0nullnull9234↑ 1running nowon the stack, waitingreturned its valuenot called yet

countLeaves(2.right) = countLeaves(null): empty tree → return 0.

nullnullnull0nullnull9234↑ 1↑ 1running nowon the stack, waitingreturned its valuenot called yet

countLeaves(2) = 1 + 0 = 1 is returned.

nullnullnull0nullnull9234↑ 1↑ 1↑ 1running nowon the stack, waitingreturned its valuenot called yet

countLeaves(3) (right child of 9): both children are null → a leaf; return 1 with no further calls.

nullnullnull0nullnull9234↑ 1↑ 1↑ 1↑ 2running nowon the stack, waitingreturned its valuenot called yet

countLeaves(9) = 1 + 1 = 2 is returned. Result: 2

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);
}

Traversals · Step through the code

Code Trace: Building a Tree from Preorder + Inorder

pre102142533465pin402152133465[0..5]1

pre[0] = 1 → new node (the root). In in[], m = 3: [0..2] becomes the left subtree, [4..5] the right subtree.

pre102142533465pin402152133465[0..2]12

pre[1] = 2 → new node (left child of 1). In in[], m = 1: [0..0] becomes the left subtree, [2..2] the right subtree.

pre102142533465pin402152133465[0..0]124

pre[2] = 4 → new node (left child of 2); m = 0. Both ranges are empty → both calls return null: 4 is a leaf.

pre102142533465pin402152133465[2..2]1245

pre[3] = 5 → new node (right child of 2); m = 2. Both ranges are empty → both calls return null: 5 is a leaf.

pre102142533465pin402152133465[0..2]1245

2's subtrees are built → return node(2); 1.left = 2.

pre102142533465pin402152133465[4..5]12453

pre[4] = 3 → new node (right child of 1). In in[], m = 4: [4..3] becomes the left subtree, [5..5] the right subtree.

pre102142533465pin402152133465empty: 4 > 312453

The left range [4..3] is empty → build returns null at once: 3 has no left child.

pre102142533465pin402152133465[5..5]124536

pre[5] = 6 → new node (right child of 3); m = 5. Both ranges are empty → both calls return null: 6 is a leaf.

pre102142533465pin402152133465[4..5]124536

3's subtrees are built → return node(3); 1.right = 3.

pre102142533465pin402152133465[0..5]124536

1's subtrees are built → return node(1), which is assigned to root. Result: 1(2(4, 5), 3(—, 6)). The search for m makes this O(n2); keeping the in[] indices in a map makes it O(n).

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;
}

Traversals

Cost of Traversals

MethodTimeExtra spaceWhy?
Recursive pre/in/postO(n)O(h)At most h + 2 frames on the call stack (counting the null call)
Pre/in/post with a stackO(n)O(h)An explicit stack takes the place of recursion
Level order with a queueO(n)O(w)w: the number of nodes on the widest level
How big is h?In a balanced tree h ≈ log2 n; in a skewed tree h = n − 1. On very deep trees recursion can throw StackOverflowError → prefer the stack-based version.
How big is w?A perfect tree has ≈ n/2 nodes on its last level, so level order uses more memory on wide trees.

Lab · Step through the Java code

Lab: Traversal with a Stack and a Queue

Summary

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 treesfull: 0 or 2 children · complete: last level fills from the left · perfect: every level full
Representationarray: index formulas · linked: left/right references
Traversalpreorder N-L-R · inorder L-N-R · postorder L-R-N · level order with a queue
Costevery traversal is O(n); extra space O(h) (stack) or O(w) (queue)

Summary

Interactive Simulators

General Tree SimulatorInsert and delete nodes in a tree with any number of children, and run traversals.
Binary Search Tree Traversal SimulatorInsert into a binary tree and watch preorder, inorder, and postorder traversals.

Summary

Common Mistakes

Forgetting the base caseAccessing node.left without first checking node == null throws a NullPointerException. Every recursive tree method starts with the empty tree.
Mixing up height definitionsCounting edges: a leaf has height 0, the empty tree −1. Counting nodes: a leaf has height 1, the empty tree 0. Pick one definition and stick to it.
Full ≠ completeFull: every node has 0 or 2 children. Complete: every level full except the last, which fills from the left. Neither implies the other.
Building a tree from one traversalA single order does not determine the tree. You need inorder plus preorder or postorder; preorder plus postorder is ambiguous when a node has only one child.
Mixing up the index formulas1-based: 2i, 2i + 1, ⌊i/2⌋. 0-based: 2i + 1, 2i + 2, ⌊(i − 1)/2⌋.
Recursion on deep treesA skewed tree has h = n − 1: hundreds of thousands of frames throw StackOverflowError → use the stack- or queue-based version.

Summary

Sample Problems

  1. Write the preorder, inorder, postorder, and level-order outputs of the tree on the right.
  2. What is the height of the tree? Which nodes are leaves? What is the depth of E?
  3. Is this tree full, complete, or perfect?
  4. In a 1-based array representation, at which indices do F and G go? How many cells must the array have at least?
  5. At most how many nodes can a binary tree of height 4 have?
  6. Preorder: A B D E C F, inorder: D B E A C F. Draw the tree.
ABCDEFG
Show answers
  1. Preorder: A B D E G C F · inorder: D B G E A C F · postorder: D G E B F C A · level order: A B C D E F G
  2. Height 3 (A→B→E→G); leaves D, G, F; E has depth 2.
  3. None: E and C each have one child (not full); position 6 is empty while 7 is filled (not complete).
  4. F: right child of C → 2·3 + 1 = 7; G: left child of E → 2·5 = 10. At least 10 cells (1..10).
  5. 25 − 1 = 31.
  6. A is the root; left subtree {D, B, E}: B is its root, D left, E right; right subtree {C, F}: C is its root, F its right child.

Summary

Exercises

  1. int countInternal(TreeNode node): write a method that counts the internal nodes.
  2. void mirror(TreeNode node): turn the tree into its mirror image (swap left ↔ right at every node).
  3. boolean isSameTree(TreeNode a, TreeNode b): are the two trees identical in shape and values?
  4. int countAtDepth(TreeNode node, int d): count the nodes at depth d.
  5. Postorder: D E B F C A, inorder: D B E A F C. Draw the tree.
  6. How many leaves does a full binary tree with n nodes have? Can n be even?
Answers / hints
  1. null or a leaf → 0; otherwise 1 + left + right. O(n).
  2. In postorder: mirror(node.left); mirror(node.right); then swap node.left and node.right.
  3. Both null → true; one null → false; otherwise data equal && lefts equal && rights equal.
  4. d == 0 → 1; otherwise countAtDepth(node.left, d−1) + countAtDepth(node.right, d−1); null → 0.
  5. The root is A (last); left {D, B, E}: B(D, E); right {F, C}: C, with F as its left child → A(B(D, E), C(F, —)).
  6. l = i + 1 and n = i + l → l = (n + 1)/2; n = 2i + 1 is always odd.

Summary

References

  • Goodrich, Tamassia, and Goldwasser, Data Structures and Algorithms in Java, 6th ed., 2014.
  • Cormen, Leiserson, Rivest, and Stein, Introduction to Algorithms, 4th ed., 2022.
  • Sedgewick and Wayne, Algorithms, 4th ed., 2011.

The End

Chapter 9: Trees
Next deck: Chapter 10: Search Trees · Study questions · Simulators: General Tree Simulator, Binary Search Tree Traversal Simulator · Glossary
1 / 1 Sercan KÜLCÜ. All rights reserved.