hello-algo/en/docs/chapter_tree/array_representation_of_tree.md

502 lines
16 KiB
Markdown
Raw Normal View History

2024-04-02 19:00:01 +08:00
---
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.
2024-05-01 07:30:10 +08:00
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.
2024-04-02 19:00:01 +08:00
![Array representation of a perfect binary tree](array_representation_of_tree.assets/array_representation_binary_tree.png){ class="animation-figure" }
<p align="center"> Figure 7-12 &nbsp; Array representation of a perfect binary tree </p>
**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 &nbsp; 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**.
2024-05-01 07:30:10 +08:00
As shown in Figure 7-13, given a non-perfect binary tree, the above method of array representation fails.
2024-04-02 19:00:01 +08:00
![Level-order traversal sequence corresponds to multiple binary tree possibilities](array_representation_of_tree.assets/array_representation_without_empty.png){ class="animation-figure" }
<p align="center"> Figure 7-13 &nbsp; Level-order traversal sequence corresponds to multiple binary tree possibilities </p>
2024-05-01 07:30:10 +08:00
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:
2024-04-02 19:00:01 +08:00
=== "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<int> 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<int?> 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" }
<p align="center"> Figure 7-14 &nbsp; Array representation of any type of binary tree </p>
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**.
2024-05-01 07:30:10 +08:00
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.
2024-04-02 19:00:01 +08:00
![Array representation of a complete binary tree](array_representation_of_tree.assets/array_representation_complete_binary_tree.png){ class="animation-figure" }
<p align="center"> Figure 7-15 &nbsp; Array representation of a complete binary tree </p>
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 preorder, inorder, postorder, and level-order traversal sequences.
=== "Python"
```python title="array_binary_tree.py"
class ArrayBinaryTree:
2024-05-06 05:27:10 +08:00
"""Array-based binary tree class"""
2024-04-02 19:00:01 +08:00
def __init__(self, arr: list[int | None]):
2024-05-06 05:27:10 +08:00
"""Constructor"""
2024-04-02 19:00:01 +08:00
self._tree = list(arr)
def size(self):
2024-05-06 05:27:10 +08:00
"""List capacity"""
2024-04-02 19:00:01 +08:00
return len(self._tree)
2024-04-29 15:19:01 +08:00
def val(self, i: int) -> int | None:
2024-05-06 05:27:10 +08:00
"""Get the value of the node at index i"""
# If the index is out of bounds, return None, representing a vacancy
2024-04-02 19:00:01 +08:00
if i < 0 or i >= self.size():
return None
return self._tree[i]
def left(self, i: int) -> int | None:
2024-05-06 05:27:10 +08:00
"""Get the index of the left child of the node at index i"""
2024-04-02 19:00:01 +08:00
return 2 * i + 1
def right(self, i: int) -> int | None:
2024-05-06 05:27:10 +08:00
"""Get the index of the right child of the node at index i"""
2024-04-02 19:00:01 +08:00
return 2 * i + 2
def parent(self, i: int) -> int | None:
2024-05-06 05:27:10 +08:00
"""Get the index of the parent of the node at index i"""
2024-04-02 19:00:01 +08:00
return (i - 1) // 2
def level_order(self) -> list[int]:
2024-05-06 05:27:10 +08:00
"""Level-order traversal"""
2024-04-02 19:00:01 +08:00
self.res = []
2024-05-06 05:27:10 +08:00
# Traverse array
2024-04-02 19:00:01 +08:00
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):
2024-05-06 05:27:10 +08:00
"""Depth-first traversal"""
2024-04-02 19:00:01 +08:00
if self.val(i) is None:
return
2024-05-06 05:27:10 +08:00
# Pre-order traversal
2024-04-02 19:00:01 +08:00
if order == "pre":
self.res.append(self.val(i))
self.dfs(self.left(i), order)
2024-05-06 05:27:10 +08:00
# In-order traversal
2024-04-02 19:00:01 +08:00
if order == "in":
self.res.append(self.val(i))
self.dfs(self.right(i), order)
2024-05-06 05:27:10 +08:00
# Post-order traversal
2024-04-02 19:00:01 +08:00
if order == "post":
self.res.append(self.val(i))
def pre_order(self) -> list[int]:
2024-05-06 05:27:10 +08:00
"""Pre-order traversal"""
2024-04-02 19:00:01 +08:00
self.res = []
self.dfs(0, order="pre")
return self.res
def in_order(self) -> list[int]:
2024-05-06 05:27:10 +08:00
"""In-order traversal"""
2024-04-02 19:00:01 +08:00
self.res = []
self.dfs(0, order="in")
return self.res
def post_order(self) -> list[int]:
2024-05-06 05:27:10 +08:00
"""Post-order traversal"""
2024-04-02 19:00:01 +08:00
self.res = []
self.dfs(0, order="post")
return self.res
```
=== "C++"
```cpp title="array_binary_tree.cpp"
2024-05-06 14:40:36 +08:00
/* Array-based binary tree class */
class ArrayBinaryTree {
public:
/* Constructor */
ArrayBinaryTree(vector<int> 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<int> levelOrder() {
vector<int> 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<int> preOrder() {
vector<int> res;
dfs(0, "pre", res);
return res;
}
/* In-order traversal */
vector<int> inOrder() {
vector<int> res;
dfs(0, "in", res);
return res;
}
/* Post-order traversal */
vector<int> postOrder() {
vector<int> res;
dfs(0, "post", res);
return res;
}
private:
vector<int> tree;
/* Depth-first traversal */
void dfs(int i, string order, vector<int> &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));
}
};
2024-04-02 19:00:01 +08:00
```
=== "Java"
```java title="array_binary_tree.java"
2024-05-06 05:27:10 +08:00
/* Array-based binary tree class */
2024-04-02 19:00:01 +08:00
class ArrayBinaryTree {
private List<Integer> tree;
2024-05-06 05:27:10 +08:00
/* Constructor */
2024-04-02 19:00:01 +08:00
public ArrayBinaryTree(List<Integer> arr) {
tree = new ArrayList<>(arr);
}
2024-05-06 05:27:10 +08:00
/* List capacity */
2024-04-02 19:00:01 +08:00
public int size() {
return tree.size();
}
2024-05-06 05:27:10 +08:00
/* Get the value of the node at index i */
2024-04-02 19:00:01 +08:00
public Integer val(int i) {
2024-05-06 05:27:10 +08:00
// If the index is out of bounds, return null, representing an empty spot
2024-04-02 19:00:01 +08:00
if (i < 0 || i >= size())
return null;
return tree.get(i);
}
2024-05-06 05:27:10 +08:00
/* Get the index of the left child of the node at index i */
2024-04-02 19:00:01 +08:00
public Integer left(int i) {
return 2 * i + 1;
}
2024-05-06 05:27:10 +08:00
/* Get the index of the right child of the node at index i */
2024-04-02 19:00:01 +08:00
public Integer right(int i) {
return 2 * i + 2;
}
2024-05-06 05:27:10 +08:00
/* Get the index of the parent of the node at index i */
2024-04-02 19:00:01 +08:00
public Integer parent(int i) {
return (i - 1) / 2;
}
2024-05-06 05:27:10 +08:00
/* Level-order traversal */
2024-04-02 19:00:01 +08:00
public List<Integer> levelOrder() {
List<Integer> res = new ArrayList<>();
2024-05-06 05:27:10 +08:00
// Traverse array
2024-04-02 19:00:01 +08:00
for (int i = 0; i < size(); i++) {
if (val(i) != null)
res.add(val(i));
}
return res;
}
2024-05-06 05:27:10 +08:00
/* Depth-first traversal */
2024-04-02 19:00:01 +08:00
private void dfs(Integer i, String order, List<Integer> res) {
2024-05-06 05:27:10 +08:00
// If it is an empty spot, return
2024-04-02 19:00:01 +08:00
if (val(i) == null)
return;
2024-05-06 05:27:10 +08:00
// Pre-order traversal
2024-04-02 19:00:01 +08:00
if ("pre".equals(order))
res.add(val(i));
dfs(left(i), order, res);
2024-05-06 05:27:10 +08:00
// In-order traversal
2024-04-02 19:00:01 +08:00
if ("in".equals(order))
res.add(val(i));
dfs(right(i), order, res);
2024-05-06 05:27:10 +08:00
// Post-order traversal
2024-04-02 19:00:01 +08:00
if ("post".equals(order))
res.add(val(i));
}
2024-05-06 05:27:10 +08:00
/* Pre-order traversal */
2024-04-02 19:00:01 +08:00
public List<Integer> preOrder() {
List<Integer> res = new ArrayList<>();
dfs(0, "pre", res);
return res;
}
2024-05-06 05:27:10 +08:00
/* In-order traversal */
2024-04-02 19:00:01 +08:00
public List<Integer> inOrder() {
List<Integer> res = new ArrayList<>();
dfs(0, "in", res);
return res;
}
2024-05-06 05:27:10 +08:00
/* Post-order traversal */
2024-04-02 19:00:01 +08:00
public List<Integer> postOrder() {
List<Integer> res = new ArrayList<>();
dfs(0, "post", res);
return res;
}
}
```
=== "C#"
```csharp title="array_binary_tree.cs"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "Go"
```go title="array_binary_tree.go"
2024-05-06 05:27:10 +08:00
[class]{arrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "Swift"
```swift title="array_binary_tree.swift"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "JS"
```javascript title="array_binary_tree.js"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "TS"
```typescript title="array_binary_tree.ts"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "Dart"
```dart title="array_binary_tree.dart"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "Rust"
```rust title="array_binary_tree.rs"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "C"
```c title="array_binary_tree.c"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "Kotlin"
```kotlin title="array_binary_tree.kt"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "Ruby"
```ruby title="array_binary_tree.rb"
2024-05-06 05:27:10 +08:00
[class]{ArrayBinaryTree}-[func]{}
2024-04-02 19:00:01 +08:00
```
=== "Zig"
```zig title="array_binary_tree.zig"
[class]{ArrayBinaryTree}-[func]{}
```
## 7.3.3 &nbsp; 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.