General trees
Trees with any number of children: n-ary traversals, parent arrays, the first-child / next-sibling encoding, folder hierarchies and forests.
Learning goals
- Traverse a tree whose nodes hold lists of children
- Build a tree from a parent array
- Convert a general tree into a binary tree and back
- Print a hierarchy as an indented listing, and handle a forest
Prerequisites
Folders, organisation charts and the DOM have nodes with many children. The ideas from binary trees carry over; only the loop over children replaces left and right.
N-ary tree traversal
In an **n-ary tree** a node keeps a list of children. The traversals are the binary ones with a loop in place of the two calls: **preorder** visits the node and then each child in turn, **postorder** visits the children and then the node, and **level order** uses a queue and adds all the children. Inorder is not defined for general trees, since there is no 'between'.
On the parent array “-1 0 0 1 1 2 2 3” (node 0 is the root with children 1 and 2; 1 has children 3 and 4; 2 has 5 and 6; 3 has 7) preorder is [0, 1, 3, 7, 4, 2, 5, 6], postorder is [7, 3, 4, 1, 5, 6, 2, 0] and level order is [0, 1, 2, 3, 4, 5, 6, 7].
1static class Node {2int val;3List<Node> children = new ArrayList<>();4Node(int v) { val = v; }5}6static void preorder(Node n, List<Integer> out) {7out.add(n.val);8for (Node c : n.children) preorder(c, out);9}10static void postorder(Node n, List<Integer> out) {11for (Node c : n.children) postorder(c, out);12out.add(n.val);13}14static List<Integer> levelOrder(Node root) {15List<Integer> out = new ArrayList<>();16Deque<Node> queue = new ArrayDeque<>(List.of(root));17while (!queue.isEmpty()) {18Node n = queue.poll();19out.add(n.val);20queue.addAll(n.children);21}22return out;23}
Best
O(n)
Average
O(n)
Worst
O(n)
Space
O(h)
General trees from a parent array: traversals and first-child / next-sibling
Interactive · edit the inputgeneral tree · 8 nodes
A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right.
1static class Node { int val; List<Node> children = new ArrayList<>(); Node(int v) { val = v; } }2static Node[] fromParents(int[] parent) { // parent[i] = index of i's parent, -1 for a root3Node[] nodes = new Node[parent.length];4for (int i = 0; i < nodes.length; i++) nodes[i] = new Node(i);5for (int i = 0; i < nodes.length; i++) if (parent[i] >= 0) nodes[parent[i]].children.add(nodes[i]);6return nodes;7}8static void preorder(Node n, List<Integer> out) {9out.add(n.val);10for (Node c : n.children) preorder(c, out);11}12static void postorder(Node n, List<Integer> out) {13for (Node c : n.children) postorder(c, out);14out.add(n.val);15}16static List<Integer> levelOrder(Node root) {17List<Integer> out = new ArrayList<>();18Deque<Node> queue = new ArrayDeque<>(List.of(root));19while (!queue.isEmpty()) {20Node n = queue.poll();21out.add(n.val);22queue.addAll(n.children);23}24return out;25}26static void printTree(Node n, int depth, StringBuilder sb) { // indented listing, like a folder tree27sb.append(" ".repeat(depth)).append(n.val).append('\n');28for (Node c : n.children) printTree(c, depth + 1, sb);29}30static class BNode { int val; BNode firstChild, nextSibling; BNode(int v) { val = v; } }31static BNode toBinary(Node n) { // first child on the left, next sibling on the right32BNode b = new BNode(n.val);33BNode last = null;34for (Node c : n.children) {35BNode child = toBinary(c);36if (last == null) b.firstChild = child; else last.nextSibling = child;37last = child;38}39return b;40}
Live state
nodes
8
roots
1
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L8 | nodes=8 roots=1 | A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right. |
| 2 | L9 | visited=1 | Visit 0 before its children. |
| 3 | L9 | visited=2 | Visit 1 before its children. |
| 4 | L9 | visited=3 | Visit 3 before its children. |
| 5 | L9 | visited=4 | Visit 7 before its children. |
| 6 | L9 | visited=5 | Visit 4 before its children. |
| 7 | L9 | visited=6 | Visit 2 before its children. |
| 8 | L9 | visited=7 | Visit 5 before its children. |
| 9 | L9 | visited=8 | Visit 6 before its children. |
| 10 | L8 | visited=8 | Preorder: [0, 1, 3, 7, 4, 2, 5, 6]. |
Parent array
A tree with n numbered nodes can be stored in an int array where parent[i] is the parent of node i and -1 marks a root. It is the most compact representation: n integers, no objects, and it is the form in which many problems arrive. Going **up** is a single array lookup; going down needs a first pass that builds the children lists.
The array also encodes forests: several -1 entries mean several roots. Validation is worth doing: every value must be -1 or a valid index, and following parents from any node must reach a root and not loop.
1static Node[] fromParents(int[] parent) { // parent[i] = index of i's parent, -1 for a root2Node[] nodes = new Node[parent.length];3for (int i = 0; i < nodes.length; i++) nodes[i] = new Node(i);4for (int i = 0; i < nodes.length; i++) if (parent[i] >= 0) nodes[parent[i]].children.add(nodes[i]);5return nodes;6}7static int depthOf(int[] parent, int i) {8int d = 0;9while (parent[i] != -1) { i = parent[i]; d++; } // walk up to the root10return d;11}
General trees from a parent array: traversals and first-child / next-sibling
Interactive · edit the inputgeneral tree · 8 nodes
A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right.
1static class Node { int val; List<Node> children = new ArrayList<>(); Node(int v) { val = v; } }2static Node[] fromParents(int[] parent) { // parent[i] = index of i's parent, -1 for a root3Node[] nodes = new Node[parent.length];4for (int i = 0; i < nodes.length; i++) nodes[i] = new Node(i);5for (int i = 0; i < nodes.length; i++) if (parent[i] >= 0) nodes[parent[i]].children.add(nodes[i]);6return nodes;7}8static void preorder(Node n, List<Integer> out) {9out.add(n.val);10for (Node c : n.children) preorder(c, out);11}12static void postorder(Node n, List<Integer> out) {13for (Node c : n.children) postorder(c, out);14out.add(n.val);15}16static List<Integer> levelOrder(Node root) {17List<Integer> out = new ArrayList<>();18Deque<Node> queue = new ArrayDeque<>(List.of(root));19while (!queue.isEmpty()) {20Node n = queue.poll();21out.add(n.val);22queue.addAll(n.children);23}24return out;25}26static void printTree(Node n, int depth, StringBuilder sb) { // indented listing, like a folder tree27sb.append(" ".repeat(depth)).append(n.val).append('\n');28for (Node c : n.children) printTree(c, depth + 1, sb);29}30static class BNode { int val; BNode firstChild, nextSibling; BNode(int v) { val = v; } }31static BNode toBinary(Node n) { // first child on the left, next sibling on the right32BNode b = new BNode(n.val);33BNode last = null;34for (Node c : n.children) {35BNode child = toBinary(c);36if (last == null) b.firstChild = child; else last.nextSibling = child;37last = child;38}39return b;40}
Live state
nodes
8
roots
1
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L8 | nodes=8 roots=1 | A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right. |
| 2 | L9 | visited=1 | Visit 0 before its children. |
| 3 | L9 | visited=2 | Visit 1 before its children. |
| 4 | L9 | visited=3 | Visit 3 before its children. |
| 5 | L9 | visited=4 | Visit 7 before its children. |
| 6 | L9 | visited=5 | Visit 4 before its children. |
| 7 | L9 | visited=6 | Visit 2 before its children. |
| 8 | L9 | visited=7 | Visit 5 before its children. |
| 9 | L9 | visited=8 | Visit 6 before its children. |
| 10 | L8 | visited=8 | Preorder: [0, 1, 3, 7, 4, 2, 5, 6]. |
First-child/next-sibling
Every general tree can be stored as a **binary tree** with the **first-child / next-sibling** encoding: the left link of a node points to its first child, and the right link points to its next sibling. A node's children are then the left child followed by the chain of right links. The result has the same n nodes and no new ones, so general-tree problems can reuse binary-tree code.
The “First-child / next-sibling” preset converts “-1 0 0 0 1 1” (node 0 has children 1, 2 and 3; node 1 has children 4 and 5): node 1 is the left child of 0, node 2 the right child of 1, node 3 the right child of 2, node 4 the left child of 1, and node 5 the right child of 4. The binary tree's height is not the general tree's height: wide trees become tall.
1static class BNode {2int val;3BNode firstChild, nextSibling;4BNode(int v) { val = v; }5}6static BNode toBinary(Node n) { // first child on the left, next sibling on the right7BNode b = new BNode(n.val);8BNode last = null;9for (Node c : n.children) {10BNode child = toBinary(c);11if (last == null) b.firstChild = child; else last.nextSibling = child;12last = child;13}14return b;15}
General trees from a parent array: traversals and first-child / next-sibling
Interactive · edit the inputgeneral tree · 8 nodes
A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right.
1static class Node { int val; List<Node> children = new ArrayList<>(); Node(int v) { val = v; } }2static Node[] fromParents(int[] parent) { // parent[i] = index of i's parent, -1 for a root3Node[] nodes = new Node[parent.length];4for (int i = 0; i < nodes.length; i++) nodes[i] = new Node(i);5for (int i = 0; i < nodes.length; i++) if (parent[i] >= 0) nodes[parent[i]].children.add(nodes[i]);6return nodes;7}8static void preorder(Node n, List<Integer> out) {9out.add(n.val);10for (Node c : n.children) preorder(c, out);11}12static void postorder(Node n, List<Integer> out) {13for (Node c : n.children) postorder(c, out);14out.add(n.val);15}16static List<Integer> levelOrder(Node root) {17List<Integer> out = new ArrayList<>();18Deque<Node> queue = new ArrayDeque<>(List.of(root));19while (!queue.isEmpty()) {20Node n = queue.poll();21out.add(n.val);22queue.addAll(n.children);23}24return out;25}26static void printTree(Node n, int depth, StringBuilder sb) { // indented listing, like a folder tree27sb.append(" ".repeat(depth)).append(n.val).append('\n');28for (Node c : n.children) printTree(c, depth + 1, sb);29}30static class BNode { int val; BNode firstChild, nextSibling; BNode(int v) { val = v; } }31static BNode toBinary(Node n) { // first child on the left, next sibling on the right32BNode b = new BNode(n.val);33BNode last = null;34for (Node c : n.children) {35BNode child = toBinary(c);36if (last == null) b.firstChild = child; else last.nextSibling = child;37last = child;38}39return b;40}
Live state
nodes
8
roots
1
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L8 | nodes=8 roots=1 | A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right. |
| 2 | L9 | visited=1 | Visit 0 before its children. |
| 3 | L9 | visited=2 | Visit 1 before its children. |
| 4 | L9 | visited=3 | Visit 3 before its children. |
| 5 | L9 | visited=4 | Visit 7 before its children. |
| 6 | L9 | visited=5 | Visit 4 before its children. |
| 7 | L9 | visited=6 | Visit 2 before its children. |
| 8 | L9 | visited=7 | Visit 5 before its children. |
| 9 | L9 | visited=8 | Visit 6 before its children. |
| 10 | L8 | visited=8 | Preorder: [0, 1, 3, 7, 4, 2, 5, 6]. |
Folder hierarchy
A file system is a tree: folders are internal nodes and files are leaves. An **indented listing**, like the output of the tree command, is a preorder traversal that passes the depth down and indents each line by it. Sizes work as postorder: a folder's size is its own plus the sizes of its children, computed after the children return.
The “Folder listing” preset prints the parent array “-1 0 0 1 1 2 4” with each dot marking one level of nesting. The same two traversals answer most folder questions: listing and totalling.
1static void printTree(Node n, int depth, StringBuilder sb) { // preorder with the depth passed down2sb.append(" ".repeat(depth)).append(n.val).append('\n');3for (Node c : n.children) printTree(c, depth + 1, sb);4}5static int totalSize(Node n, int[] size) { // postorder: children first6int total = size[n.val];7for (Node c : n.children) total += totalSize(c, size);8return total;9}
General trees from a parent array: traversals and first-child / next-sibling
Interactive · edit the inputgeneral tree · 8 nodes
A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right.
1static class Node { int val; List<Node> children = new ArrayList<>(); Node(int v) { val = v; } }2static Node[] fromParents(int[] parent) { // parent[i] = index of i's parent, -1 for a root3Node[] nodes = new Node[parent.length];4for (int i = 0; i < nodes.length; i++) nodes[i] = new Node(i);5for (int i = 0; i < nodes.length; i++) if (parent[i] >= 0) nodes[parent[i]].children.add(nodes[i]);6return nodes;7}8static void preorder(Node n, List<Integer> out) {9out.add(n.val);10for (Node c : n.children) preorder(c, out);11}12static void postorder(Node n, List<Integer> out) {13for (Node c : n.children) postorder(c, out);14out.add(n.val);15}16static List<Integer> levelOrder(Node root) {17List<Integer> out = new ArrayList<>();18Deque<Node> queue = new ArrayDeque<>(List.of(root));19while (!queue.isEmpty()) {20Node n = queue.poll();21out.add(n.val);22queue.addAll(n.children);23}24return out;25}26static void printTree(Node n, int depth, StringBuilder sb) { // indented listing, like a folder tree27sb.append(" ".repeat(depth)).append(n.val).append('\n');28for (Node c : n.children) printTree(c, depth + 1, sb);29}30static class BNode { int val; BNode firstChild, nextSibling; BNode(int v) { val = v; } }31static BNode toBinary(Node n) { // first child on the left, next sibling on the right32BNode b = new BNode(n.val);33BNode last = null;34for (Node c : n.children) {35BNode child = toBinary(c);36if (last == null) b.firstChild = child; else last.nextSibling = child;37last = child;38}39return b;40}
Live state
nodes
8
roots
1
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L8 | nodes=8 roots=1 | A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right. |
| 2 | L9 | visited=1 | Visit 0 before its children. |
| 3 | L9 | visited=2 | Visit 1 before its children. |
| 4 | L9 | visited=3 | Visit 3 before its children. |
| 5 | L9 | visited=4 | Visit 7 before its children. |
| 6 | L9 | visited=5 | Visit 4 before its children. |
| 7 | L9 | visited=6 | Visit 2 before its children. |
| 8 | L9 | visited=7 | Visit 5 before its children. |
| 9 | L9 | visited=8 | Visit 6 before its children. |
| 10 | L8 | visited=8 | Preorder: [0, 1, 3, 7, 4, 2, 5, 6]. |
Forest traversal
A forest is traversed by traversing each of its trees in turn: loop over the roots and call the tree traversal on each, or add a virtual root whose children are the real roots, so one call handles everything. Level order starts with all roots in the queue.
The “A forest (two roots)” preset has the parent array “-1 0 -1 2 2 0”: root 0 (with children 1 and 5) and root 2 (with children 3 and 4). Preorder gives [0, 1, 5, 2, 3, 4]: the first tree fully, then the second.
1static List<Integer> forestPreorder(int[] parent) {2Node[] nodes = fromParents(parent);3List<Integer> out = new ArrayList<>();4for (int i = 0; i < parent.length; i++) {5if (parent[i] == -1) preorder(nodes[i], out); // one traversal per root6}7return out;8}9static Node[] fromParents(int[] parent) {10Node[] nodes = new Node[parent.length];11for (int i = 0; i < nodes.length; i++) nodes[i] = new Node(i);12for (int i = 0; i < nodes.length; i++) if (parent[i] >= 0) nodes[parent[i]].children.add(nodes[i]);13return nodes;14}15static void preorder(Node n, List<Integer> out) {16out.add(n.val);17for (Node c : n.children) preorder(c, out);18}
General trees from a parent array: traversals and first-child / next-sibling
Interactive · edit the inputgeneral tree · 8 nodes
A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right.
1static class Node { int val; List<Node> children = new ArrayList<>(); Node(int v) { val = v; } }2static Node[] fromParents(int[] parent) { // parent[i] = index of i's parent, -1 for a root3Node[] nodes = new Node[parent.length];4for (int i = 0; i < nodes.length; i++) nodes[i] = new Node(i);5for (int i = 0; i < nodes.length; i++) if (parent[i] >= 0) nodes[parent[i]].children.add(nodes[i]);6return nodes;7}8static void preorder(Node n, List<Integer> out) {9out.add(n.val);10for (Node c : n.children) preorder(c, out);11}12static void postorder(Node n, List<Integer> out) {13for (Node c : n.children) postorder(c, out);14out.add(n.val);15}16static List<Integer> levelOrder(Node root) {17List<Integer> out = new ArrayList<>();18Deque<Node> queue = new ArrayDeque<>(List.of(root));19while (!queue.isEmpty()) {20Node n = queue.poll();21out.add(n.val);22queue.addAll(n.children);23}24return out;25}26static void printTree(Node n, int depth, StringBuilder sb) { // indented listing, like a folder tree27sb.append(" ".repeat(depth)).append(n.val).append('\n');28for (Node c : n.children) printTree(c, depth + 1, sb);29}30static class BNode { int val; BNode firstChild, nextSibling; BNode(int v) { val = v; } }31static BNode toBinary(Node n) { // first child on the left, next sibling on the right32BNode b = new BNode(n.val);33BNode last = null;34for (Node c : n.children) {35BNode child = toBinary(c);36if (last == null) b.firstChild = child; else last.nextSibling = child;37last = child;38}39return b;40}
Live state
nodes
8
roots
1
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L8 | nodes=8 roots=1 | A general tree node has any number of children, kept in a list, so the traversals loop over the children instead of calling left and right. |
| 2 | L9 | visited=1 | Visit 0 before its children. |
| 3 | L9 | visited=2 | Visit 1 before its children. |
| 4 | L9 | visited=3 | Visit 3 before its children. |
| 5 | L9 | visited=4 | Visit 7 before its children. |
| 6 | L9 | visited=5 | Visit 4 before its children. |
| 7 | L9 | visited=6 | Visit 2 before its children. |
| 8 | L9 | visited=7 | Visit 5 before its children. |
| 9 | L9 | visited=8 | Visit 6 before its children. |
| 10 | L8 | visited=8 | Preorder: [0, 1, 3, 7, 4, 2, 5, 6]. |
Practice ladder
Now try it yourself
Easy
- N-ary Tree Preorder Traversal
- N-ary Tree Postorder Traversal
- Maximum Depth of N-ary Tree
Medium
- N-ary Tree Level Order Traversal
- Serialize and Deserialize N-ary Tree
- Time Needed to Inform All Employees
Advanced
- Encode N-ary Tree to Binary Tree
- Delete Nodes And Return Forest
Key takeaways
- General-tree traversals replace the left and right calls with a loop over the children list.
- A parent array stores a tree or forest in n integers: -1 marks a root, and going up is one lookup.
- First-child / next-sibling turns any tree into a binary tree with the same nodes.
- A folder listing is preorder with the depth passed down; sizes are a postorder sum.
- Traverse a forest by looping over its roots.