Binary tree representation
How binary trees are stored in Java: the TreeNode class, left and right children, building trees recursively and from level-order data, the array representation and null children.
Learning goals
- Write a TreeNode class and build a small tree by hand
- Build a tree from level-order data with # for empty
- Store a complete tree in an array with index arithmetic
- Handle null children safely and know how many there are
Prerequisites
Before any algorithm, a tree needs a concrete form in memory. Two forms cover almost every use: linked nodes and an array.
TreeNode class
The standard linked representation is one object per node, holding a value and a reference to each child. The root reference is the tree. All the traversal and query code in this chapter takes a TreeNode as its argument and works recursively on its children.
A tree needs no wrapper class for most problems; contest-style code passes the root around. A production class would hold the root plus a size counter and guard invariants.
1static class TreeNode {2int val;3TreeNode left, right; // null means "no child on that side"4TreeNode(int val) { this.val = val; }5TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; }6}
Left/right child
The side of a child is part of the tree's identity. Building the tree 1 with children 2 and 3, where 2 has the child 4, takes explicit assignments to left and right. A node with only a right child is not the same tree as a node with only a left child, even though both have one child.
The constructor with two children makes small trees readable as nested expressions: new TreeNode(1, new TreeNode(2, new TreeNode(4), null), new TreeNode(3)).
1static TreeNode sample() {2TreeNode root = new TreeNode(1);3root.left = new TreeNode(2);4root.right = new TreeNode(3);5root.left.left = new TreeNode(4); // 4 is the LEFT child of 2; 2 has no right child6return root;7}
Recursive construction
Trees are described in tests as **level-order lists** with # (or null) for a missing child, such as “3 9 20 # # 15 7”: the root is 3, its children are 9 and 20, 9 has no children (two #), and 20 has children 15 and 7. Building the tree takes a queue: each node taken from the front receives the next two tokens as its left and right children.
Every animation in this chapter accepts exactly this text format, so you can type your own trees. A token # is consumed by a missing child but creates no node and adds nothing to the queue.
1static TreeNode fromLevelOrder(String[] tokens) {2if (tokens.length == 0 || tokens[0].equals("#")) return null;3TreeNode root = new TreeNode(Integer.parseInt(tokens[0]));4Deque<TreeNode> queue = new ArrayDeque<>(List.of(root));5int i = 1;6while (!queue.isEmpty() && i < tokens.length) {7TreeNode n = queue.poll();8if (i < tokens.length && !tokens[i++].equals("#")) { n.left = new TreeNode(Integer.parseInt(tokens[i - 1])); queue.add(n.left); }9if (i < tokens.length && !tokens[i++].equals("#")) { n.right = new TreeNode(Integer.parseInt(tokens[i - 1])); queue.add(n.right); }10}11return root;12}1314// the same idea recursively: a perfect tree of the given height, numbered by preorder15static TreeNode perfect(int height, int[] next) {16if (height < 0) return null;17TreeNode n = new TreeNode(next[0]++);18n.left = perfect(height - 1, next);19n.right = perfect(height - 1, next);20return n;21}
Binary tree traversals: preorder, inorder, postorder and level order
Interactive · edit the inputbinary tree · 6 nodes
output
Preorder traversal of a tree with 6 nodes. The recursion's call stack remembers the path back to the root.
1static void preorder(TreeNode n, List<Integer> out) {2if (n == null) return; // empty subtree: nothing to visit3out.add(n.val); // visit the node first4preorder(n.left, out);5preorder(n.right, out);6}7static void inorder(TreeNode n, List<Integer> out) {8if (n == null) return;9inorder(n.left, out);10out.add(n.val); // visit between the two subtrees11inorder(n.right, out);12}13static void postorder(TreeNode n, List<Integer> out) {14if (n == null) return;15postorder(n.left, out);16postorder(n.right, out);17out.add(n.val); // visit after both subtrees18}19static List<Integer> levelOrder(TreeNode root) {20List<Integer> out = new ArrayList<>();21Deque<TreeNode> queue = new ArrayDeque<>();22if (root != null) queue.add(root);23while (!queue.isEmpty()) {24TreeNode n = queue.poll(); // oldest node first25out.add(n.val);26if (n.left != null) queue.add(n.left);27if (n.right != null) queue.add(n.right);28}29return out;30}
Live state
visited
0
Execution trace (dry run table)Show 21 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | visited=0 | Preorder traversal of a tree with 6 nodes. The recursion's call stack remembers the path back to the root. |
| 2 | L1 | visited=0 stack depth=1 | Call preorder(1), the root. |
| 3 | L3 | visited=1 stack depth=1 | Visit 1: before either subtree. |
| 4 | L1 | visited=1 stack depth=2 | Call preorder(2), depth 1. |
| 5 | L3 | visited=2 stack depth=2 | Visit 2: before either subtree. |
| 6 | L1 | visited=2 stack depth=3 | Call preorder(4), depth 2. |
| 7 | L3 | visited=3 stack depth=3 | Visit 4: before either subtree. |
| 8 | L4 | visited=3 stack depth=3 | The left child of 4 is empty, so that call returns at once. |
| 9 | L5 | visited=3 stack depth=3 | The right child of 4 is empty, so that call returns at once. |
| 10 | L1 | visited=3 stack depth=3 | Call preorder(5), depth 2. |
| 11 | L3 | visited=4 stack depth=3 | Visit 5: before either subtree. |
| 12 | L4 | visited=4 stack depth=3 | The left child of 5 is empty, so that call returns at once. |
| 13 | L5 | visited=4 stack depth=3 | The right child of 5 is empty, so that call returns at once. |
| 14 | L1 | visited=4 stack depth=2 | Call preorder(3), depth 1. |
| 15 | L3 | visited=5 stack depth=2 | Visit 3: before either subtree. |
| 16 | L4 | visited=5 stack depth=2 | The left child of 3 is empty, so that call returns at once. |
| 17 | L1 | visited=5 stack depth=3 | Call preorder(6), depth 2. |
| 18 | L3 | visited=6 stack depth=3 | Visit 6: before either subtree. |
| 19 | L4 | visited=6 stack depth=3 | The left child of 6 is empty, so that call returns at once. |
| 20 | L5 | visited=6 stack depth=3 | The right child of 6 is empty, so that call returns at once. |
| 21 | L1 | visited=6 | Preorder: [1, 2, 4, 5, 3, 6]. |
Array representation
A **complete** binary tree fits in an array with no pointers. Store the root at index 0 and, level by level, left to right, everything else; then the children of index i are at 2i + 1 and 2i + 2, and the parent is at (i - 1) / 2 (integer division). Heaps use exactly this layout, and it is cache-friendly and compact.
For an incomplete tree the array has holes (nulls), and the waste can be huge: a chain of n left children needs 2^n - 1 slots, so a chain of 5 nodes uses a 31-slot array and a chain of 20 needs over a million. Linked nodes cost memory in proportion to n, so use the array form only when the tree is known to be nearly complete.
| Relation | Index formula (0-based) |
|---|---|
| Left child of i | 2i + 1 |
| Right child of i | 2i + 2 |
| Parent of i (i > 0) | (i - 1) / 2 |
| Nodes on level d start at | 2^d - 1 |
1static Integer[] toHeapArray(TreeNode root) {2int h = height(root);3Integer[] a = new Integer[(1 << (h + 1)) - 1]; // room for a perfect tree of this height4fill(root, 0, a);5return a;6}7static void fill(TreeNode n, int i, Integer[] a) {8if (n == null) return;9a[i] = n.val;10fill(n.left, 2 * i + 1, a);11fill(n.right, 2 * i + 2, a);12}13static int height(TreeNode n) { return n == null ? -1 : 1 + Math.max(height(n.left), height(n.right)); }
Null children
Every leaf has two null child references, and a node with one child has one. Counting them shows a fixed relation: a binary tree with n nodes has **n + 1** null references. All the base cases of tree recursion are the same null check: if (n == null) return ...;.
The cost of forgetting it is a NullPointerException at the first leaf. Choose the base value carefully, because it flows into every parent: 0 for counts and sums, -1 for height, true for 'all nodes satisfy', false for 'any node satisfies', null for 'find a node'.
1static int nullLinks(TreeNode n) {2if (n == null) return 1; // this empty link is one of the n + 13return nullLinks(n.left) + nullLinks(n.right);4}
Practice ladder
Now try it yourself
Easy
- Invert Binary Tree
- Convert Sorted Array to Binary Search Tree
Medium
- Construct Binary Tree from Preorder and Inorder Traversal
- Maximum Binary Tree
Advanced
- Serialize and Deserialize Binary Tree
- Recover a Tree From Preorder Traversal
Key takeaways
- A TreeNode holds a value and two child references; the root reference is the tree.
- Left and right are part of a tree's identity.
- Level-order text with # for empty children builds a tree with a queue.
- A complete tree packs into an array: children of i at 2i + 1 and 2i + 2.
- A binary tree with n nodes has n + 1 null references, and every recursive function needs a null base case.