--- comments: true --- # 7.3   Array representation of binary trees Under the linked list representation, the storage unit of a binary tree is a node `TreeNode`, with nodes connected by pointers. The basic operations of binary trees under the linked list representation were introduced in the previous section. So, can we use an array to represent a binary tree? The answer is yes. ## 7.3.1   Representing perfect binary trees Let's analyze a simple case first. Given a perfect binary tree, we store all nodes in an array according to the order of level-order traversal, where each node corresponds to a unique array index. Based on the characteristics of level-order traversal, we can deduce a "mapping formula" between the index of a parent node and its children: **If a node's index is $i$, then the index of its left child is $2i + 1$ and the right child is $2i + 2$**. Figure 7-12 shows the mapping relationship between the indices of various nodes. ![Array representation of a perfect binary tree](array_representation_of_tree.assets/array_representation_binary_tree.png){ class="animation-figure" }

Figure 7-12   Array representation of a perfect binary tree

**The mapping formula plays a role similar to the node references (pointers) in linked lists**. Given any node in the array, we can access its left (right) child node using the mapping formula. ## 7.3.2   Representing any binary tree Perfect binary trees are a special case; there are often many `None` values in the middle levels of a binary tree. Since the sequence of level-order traversal does not include these `None` values, we cannot solely rely on this sequence to deduce the number and distribution of `None` values. **This means that multiple binary tree structures can match the same level-order traversal sequence**. As shown in Figure 7-13, given a non-perfect binary tree, the above method of array representation fails. ![Level-order traversal sequence corresponds to multiple binary tree possibilities](array_representation_of_tree.assets/array_representation_without_empty.png){ class="animation-figure" }

Figure 7-13   Level-order traversal sequence corresponds to multiple binary tree possibilities

To solve this problem, **we can consider explicitly writing out all `None` values in the level-order traversal sequence**. As shown in Figure 7-14, after this treatment, the level-order traversal sequence can uniquely represent a binary tree. Example code is as follows: === "Python" ```python title="" # Array representation of a binary tree # Using None to represent empty slots tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` === "C++" ```cpp title="" /* Array representation of a binary tree */ // Using the maximum integer value INT_MAX to mark empty slots vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Java" ```java title="" /* Array representation of a binary tree */ // Using the Integer wrapper class allows for using null to mark empty slots Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` === "C#" ```csharp title="" /* Array representation of a binary tree */ // Using nullable int (int?) allows for using null to mark empty slots int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Go" ```go title="" /* Array representation of a binary tree */ // Using an any type slice, allowing for nil to mark empty slots tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` === "Swift" ```swift title="" /* Array representation of a binary tree */ // Using optional Int (Int?) allows for using nil to mark empty slots let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` === "JS" ```javascript title="" /* Array representation of a binary tree */ // Using null to represent empty slots let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "TS" ```typescript title="" /* Array representation of a binary tree */ // Using null to represent empty slots let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Dart" ```dart title="" /* Array representation of a binary tree */ // Using nullable int (int?) allows for using null to mark empty slots List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Rust" ```rust title="" /* Array representation of a binary tree */ // Using None to mark empty slots let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; ``` === "C" ```c title="" /* Array representation of a binary tree */ // Using the maximum int value to mark empty slots, therefore, node values must not be INT_MAX int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Kotlin" ```kotlin title="" /* Array representation of a binary tree */ // Using null to represent empty slots val tree = mutableListOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) ``` === "Ruby" ```ruby title="" ``` === "Zig" ```zig title="" ``` ![Array representation of any type of binary tree](array_representation_of_tree.assets/array_representation_with_empty.png){ class="animation-figure" }

Figure 7-14   Array representation of any type of binary tree

It's worth noting that **complete binary trees are very suitable for array representation**. Recalling the definition of a complete binary tree, `None` appears only at the bottom level and towards the right, **meaning all `None` values definitely appear at the end of the level-order traversal sequence**. This means that when using an array to represent a complete binary tree, it's possible to omit storing all `None` values, which is very convenient. Figure 7-15 gives an example. ![Array representation of a complete binary tree](array_representation_of_tree.assets/array_representation_complete_binary_tree.png){ class="animation-figure" }

Figure 7-15   Array representation of a complete binary tree

The following code implements a binary tree based on array representation, including the following operations: - Given a node, obtain its value, left (right) child node, and parent node. - Obtain the pre-order, in-order, post-order, and level-order traversal sequences. === "Python" ```python title="array_binary_tree.py" class ArrayBinaryTree: """Array-based binary tree class""" def __init__(self, arr: list[int | None]): """Constructor""" self._tree = list(arr) def size(self): """List capacity""" return len(self._tree) def val(self, i: int) -> int | None: """Get the value of the node at index i""" # If the index is out of bounds, return None, representing a vacancy if i < 0 or i >= self.size(): return None return self._tree[i] def left(self, i: int) -> int | None: """Get the index of the left child of the node at index i""" return 2 * i + 1 def right(self, i: int) -> int | None: """Get the index of the right child of the node at index i""" return 2 * i + 2 def parent(self, i: int) -> int | None: """Get the index of the parent of the node at index i""" return (i - 1) // 2 def level_order(self) -> list[int]: """Level-order traversal""" self.res = [] # Traverse array for i in range(self.size()): if self.val(i) is not None: self.res.append(self.val(i)) return self.res def dfs(self, i: int, order: str): """Depth-first traversal""" if self.val(i) is None: return # Pre-order traversal if order == "pre": self.res.append(self.val(i)) self.dfs(self.left(i), order) # In-order traversal if order == "in": self.res.append(self.val(i)) self.dfs(self.right(i), order) # Post-order traversal if order == "post": self.res.append(self.val(i)) def pre_order(self) -> list[int]: """Pre-order traversal""" self.res = [] self.dfs(0, order="pre") return self.res def in_order(self) -> list[int]: """In-order traversal""" self.res = [] self.dfs(0, order="in") return self.res def post_order(self) -> list[int]: """Post-order traversal""" self.res = [] self.dfs(0, order="post") return self.res ``` === "C++" ```cpp title="array_binary_tree.cpp" /* Array-based binary tree class */ class ArrayBinaryTree { public: /* Constructor */ ArrayBinaryTree(vector arr) { tree = arr; } /* List capacity */ int size() { return tree.size(); } /* Get the value of the node at index i */ int val(int i) { // If index is out of bounds, return INT_MAX, representing a null if (i < 0 || i >= size()) return INT_MAX; return tree[i]; } /* Get the index of the left child of the node at index i */ int left(int i) { return 2 * i + 1; } /* Get the index of the right child of the node at index i */ int right(int i) { return 2 * i + 2; } /* Get the index of the parent of the node at index i */ int parent(int i) { return (i - 1) / 2; } /* Level-order traversal */ vector levelOrder() { vector res; // Traverse array for (int i = 0; i < size(); i++) { if (val(i) != INT_MAX) res.push_back(val(i)); } return res; } /* Pre-order traversal */ vector preOrder() { vector res; dfs(0, "pre", res); return res; } /* In-order traversal */ vector inOrder() { vector res; dfs(0, "in", res); return res; } /* Post-order traversal */ vector postOrder() { vector res; dfs(0, "post", res); return res; } private: vector tree; /* Depth-first traversal */ void dfs(int i, string order, vector &res) { // If it is an empty spot, return if (val(i) == INT_MAX) return; // Pre-order traversal if (order == "pre") res.push_back(val(i)); dfs(left(i), order, res); // In-order traversal if (order == "in") res.push_back(val(i)); dfs(right(i), order, res); // Post-order traversal if (order == "post") res.push_back(val(i)); } }; ``` === "Java" ```java title="array_binary_tree.java" /* Array-based binary tree class */ class ArrayBinaryTree { private List tree; /* Constructor */ public ArrayBinaryTree(List arr) { tree = new ArrayList<>(arr); } /* List capacity */ public int size() { return tree.size(); } /* Get the value of the node at index i */ public Integer val(int i) { // If the index is out of bounds, return null, representing an empty spot if (i < 0 || i >= size()) return null; return tree.get(i); } /* Get the index of the left child of the node at index i */ public Integer left(int i) { return 2 * i + 1; } /* Get the index of the right child of the node at index i */ public Integer right(int i) { return 2 * i + 2; } /* Get the index of the parent of the node at index i */ public Integer parent(int i) { return (i - 1) / 2; } /* Level-order traversal */ public List levelOrder() { List res = new ArrayList<>(); // Traverse array for (int i = 0; i < size(); i++) { if (val(i) != null) res.add(val(i)); } return res; } /* Depth-first traversal */ private void dfs(Integer i, String order, List res) { // If it is an empty spot, return if (val(i) == null) return; // Pre-order traversal if ("pre".equals(order)) res.add(val(i)); dfs(left(i), order, res); // In-order traversal if ("in".equals(order)) res.add(val(i)); dfs(right(i), order, res); // Post-order traversal if ("post".equals(order)) res.add(val(i)); } /* Pre-order traversal */ public List preOrder() { List res = new ArrayList<>(); dfs(0, "pre", res); return res; } /* In-order traversal */ public List inOrder() { List res = new ArrayList<>(); dfs(0, "in", res); return res; } /* Post-order traversal */ public List postOrder() { List res = new ArrayList<>(); dfs(0, "post", res); return res; } } ``` === "C#" ```csharp title="array_binary_tree.cs" [class]{ArrayBinaryTree}-[func]{} ``` === "Go" ```go title="array_binary_tree.go" [class]{arrayBinaryTree}-[func]{} ``` === "Swift" ```swift title="array_binary_tree.swift" [class]{ArrayBinaryTree}-[func]{} ``` === "JS" ```javascript title="array_binary_tree.js" [class]{ArrayBinaryTree}-[func]{} ``` === "TS" ```typescript title="array_binary_tree.ts" [class]{ArrayBinaryTree}-[func]{} ``` === "Dart" ```dart title="array_binary_tree.dart" [class]{ArrayBinaryTree}-[func]{} ``` === "Rust" ```rust title="array_binary_tree.rs" [class]{ArrayBinaryTree}-[func]{} ``` === "C" ```c title="array_binary_tree.c" [class]{ArrayBinaryTree}-[func]{} ``` === "Kotlin" ```kotlin title="array_binary_tree.kt" [class]{ArrayBinaryTree}-[func]{} ``` === "Ruby" ```ruby title="array_binary_tree.rb" [class]{ArrayBinaryTree}-[func]{} ``` === "Zig" ```zig title="array_binary_tree.zig" [class]{ArrayBinaryTree}-[func]{} ``` ## 7.3.3   Advantages and limitations The array representation of binary trees has the following advantages: - Arrays are stored in contiguous memory spaces, which is cache-friendly and allows for faster access and traversal. - It does not require storing pointers, which saves space. - It allows random access to nodes. However, the array representation also has some limitations: - Array storage requires contiguous memory space, so it is not suitable for storing trees with a large amount of data. - Adding or deleting nodes requires array insertion and deletion operations, which are less efficient. - When there are many `None` values in the binary tree, the proportion of node data contained in the array is low, leading to lower space utilization.