You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hello-algo/en/docs/chapter_tree/binary_search_tree.md

665 lines
22 KiB

8 months ago
---
comments: true
---
# 7.4   Binary search tree
7 months ago
As shown in Figure 7-16, a <u>binary search tree</u> satisfies the following conditions.
8 months ago
7 months ago
1. For the root node, the value of all nodes in the left subtree $<$ the value of the root node $<$ the value of all nodes in the right subtree.
8 months ago
2. The left and right subtrees of any node are also binary search trees, i.e., they satisfy condition `1.` as well.
![Binary search tree](binary_search_tree.assets/binary_search_tree.png){ class="animation-figure" }
<p align="center"> Figure 7-16 &nbsp; Binary search tree </p>
## 7.4.1 &nbsp; Operations on a binary search tree
We encapsulate the binary search tree as a class `BinarySearchTree` and declare a member variable `root`, pointing to the tree's root node.
### 1. &nbsp; Searching for a node
7 months ago
Given a target node value `num`, one can search according to the properties of the binary search tree. As shown in Figure 7-17, we declare a node `cur` and start from the binary tree's root node `root`, looping to compare the size relationship between the node value `cur.val` and `num`.
8 months ago
- If `cur.val < num`, it means the target node is in `cur`'s right subtree, thus execute `cur = cur.right`.
- If `cur.val > num`, it means the target node is in `cur`'s left subtree, thus execute `cur = cur.left`.
- If `cur.val = num`, it means the target node is found, exit the loop and return the node.
=== "<1>"
![Example of searching for a node in a binary search tree](binary_search_tree.assets/bst_search_step1.png){ class="animation-figure" }
=== "<2>"
![bst_search_step2](binary_search_tree.assets/bst_search_step2.png){ class="animation-figure" }
=== "<3>"
![bst_search_step3](binary_search_tree.assets/bst_search_step3.png){ class="animation-figure" }
=== "<4>"
![bst_search_step4](binary_search_tree.assets/bst_search_step4.png){ class="animation-figure" }
<p align="center"> Figure 7-17 &nbsp; Example of searching for a node in a binary search tree </p>
The search operation in a binary search tree works on the same principle as the binary search algorithm, eliminating half of the possibilities in each round. The number of loops is at most the height of the binary tree. When the binary tree is balanced, it uses $O(\log n)$ time. Example code is as follows:
=== "Python"
```python title="binary_search_tree.py"
def search(self, num: int) -> TreeNode | None:
7 months ago
"""Search node"""
8 months ago
cur = self._root
7 months ago
# Loop find, break after passing leaf nodes
8 months ago
while cur is not None:
7 months ago
# Target node is in cur's right subtree
8 months ago
if cur.val < num:
cur = cur.right
7 months ago
# Target node is in cur's left subtree
8 months ago
elif cur.val > num:
cur = cur.left
7 months ago
# Found target node, break loop
8 months ago
else:
break
return cur
```
=== "C++"
```cpp title="binary_search_tree.cpp"
6 months ago
/* Search node */
TreeNode *search(int num) {
TreeNode *cur = root;
// Loop find, break after passing leaf nodes
while (cur != nullptr) {
// Target node is in cur's right subtree
if (cur->val < num)
cur = cur->right;
// Target node is in cur's left subtree
else if (cur->val > num)
cur = cur->left;
// Found target node, break loop
else
break;
}
// Return target node
return cur;
}
8 months ago
```
=== "Java"
```java title="binary_search_tree.java"
7 months ago
/* Search node */
8 months ago
TreeNode search(int num) {
TreeNode cur = root;
7 months ago
// Loop find, break after passing leaf nodes
8 months ago
while (cur != null) {
7 months ago
// Target node is in cur's right subtree
8 months ago
if (cur.val < num)
cur = cur.right;
7 months ago
// Target node is in cur's left subtree
8 months ago
else if (cur.val > num)
cur = cur.left;
7 months ago
// Found target node, break loop
8 months ago
else
break;
}
7 months ago
// Return target node
8 months ago
return cur;
}
```
=== "C#"
```csharp title="binary_search_tree.cs"
7 months ago
[class]{BinarySearchTree}-[func]{Search}
8 months ago
```
=== "Go"
```go title="binary_search_tree.go"
7 months ago
[class]{binarySearchTree}-[func]{search}
8 months ago
```
=== "Swift"
```swift title="binary_search_tree.swift"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "JS"
```javascript title="binary_search_tree.js"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "TS"
```typescript title="binary_search_tree.ts"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "Dart"
```dart title="binary_search_tree.dart"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "Rust"
```rust title="binary_search_tree.rs"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "C"
```c title="binary_search_tree.c"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "Kotlin"
```kotlin title="binary_search_tree.kt"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "Ruby"
```ruby title="binary_search_tree.rb"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
=== "Zig"
```zig title="binary_search_tree.zig"
7 months ago
[class]{BinarySearchTree}-[func]{search}
8 months ago
```
### 2. &nbsp; Inserting a node
7 months ago
Given an element `num` to be inserted, to maintain the property of the binary search tree "left subtree < root node < right subtree," the insertion operation proceeds as shown in Figure 7-18.
8 months ago
1. **Finding the insertion position**: Similar to the search operation, start from the root node and loop downwards according to the size relationship between the current node value and `num` until passing through the leaf node (traversing to `None`) then exit the loop.
2. **Insert the node at that position**: Initialize the node `num` and place it where `None` was.
![Inserting a node into a binary search tree](binary_search_tree.assets/bst_insert.png){ class="animation-figure" }
<p align="center"> Figure 7-18 &nbsp; Inserting a node into a binary search tree </p>
In the code implementation, note the following two points.
- The binary search tree does not allow duplicate nodes; otherwise, it will violate its definition. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed, and it directly returns.
- To perform the insertion operation, we need to use the node `pre` to save the node from the last loop. This way, when traversing to `None`, we can get its parent node, thus completing the node insertion operation.
=== "Python"
```python title="binary_search_tree.py"
def insert(self, num: int):
7 months ago
"""Insert node"""
# If tree is empty, initialize root node
8 months ago
if self._root is None:
self._root = TreeNode(num)
return
7 months ago
# Loop find, break after passing leaf nodes
8 months ago
cur, pre = self._root, None
while cur is not None:
7 months ago
# Found duplicate node, thus return
8 months ago
if cur.val == num:
return
pre = cur
7 months ago
# Insertion position is in cur's right subtree
8 months ago
if cur.val < num:
cur = cur.right
7 months ago
# Insertion position is in cur's left subtree
8 months ago
else:
cur = cur.left
7 months ago
# Insert node
8 months ago
node = TreeNode(num)
if pre.val < num:
pre.right = node
else:
pre.left = node
```
=== "C++"
```cpp title="binary_search_tree.cpp"
6 months ago
/* Insert node */
void insert(int num) {
// If tree is empty, initialize root node
if (root == nullptr) {
root = new TreeNode(num);
return;
}
TreeNode *cur = root, *pre = nullptr;
// Loop find, break after passing leaf nodes
while (cur != nullptr) {
// Found duplicate node, thus return
if (cur->val == num)
return;
pre = cur;
// Insertion position is in cur's right subtree
if (cur->val < num)
cur = cur->right;
// Insertion position is in cur's left subtree
else
cur = cur->left;
}
// Insert node
TreeNode *node = new TreeNode(num);
if (pre->val < num)
pre->right = node;
else
pre->left = node;
}
8 months ago
```
=== "Java"
```java title="binary_search_tree.java"
7 months ago
/* Insert node */
8 months ago
void insert(int num) {
7 months ago
// If tree is empty, initialize root node
8 months ago
if (root == null) {
root = new TreeNode(num);
return;
}
TreeNode cur = root, pre = null;
7 months ago
// Loop find, break after passing leaf nodes
8 months ago
while (cur != null) {
7 months ago
// Found duplicate node, thus return
8 months ago
if (cur.val == num)
return;
pre = cur;
7 months ago
// Insertion position is in cur's right subtree
8 months ago
if (cur.val < num)
cur = cur.right;
7 months ago
// Insertion position is in cur's left subtree
8 months ago
else
cur = cur.left;
}
7 months ago
// Insert node
8 months ago
TreeNode node = new TreeNode(num);
if (pre.val < num)
pre.right = node;
else
pre.left = node;
}
```
=== "C#"
```csharp title="binary_search_tree.cs"
7 months ago
[class]{BinarySearchTree}-[func]{Insert}
8 months ago
```
=== "Go"
```go title="binary_search_tree.go"
7 months ago
[class]{binarySearchTree}-[func]{insert}
8 months ago
```
=== "Swift"
```swift title="binary_search_tree.swift"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "JS"
```javascript title="binary_search_tree.js"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "TS"
```typescript title="binary_search_tree.ts"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "Dart"
```dart title="binary_search_tree.dart"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "Rust"
```rust title="binary_search_tree.rs"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "C"
```c title="binary_search_tree.c"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "Kotlin"
```kotlin title="binary_search_tree.kt"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "Ruby"
```ruby title="binary_search_tree.rb"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
=== "Zig"
```zig title="binary_search_tree.zig"
7 months ago
[class]{BinarySearchTree}-[func]{insert}
8 months ago
```
Similar to searching for a node, inserting a node uses $O(\log n)$ time.
### 3. &nbsp; Removing a node
First, find the target node in the binary tree, then remove it. Similar to inserting a node, we need to ensure that after the removal operation is completed, the property of the binary search tree "left subtree < root node < right subtree" is still satisfied. Therefore, based on the number of child nodes of the target node, we divide it into 0, 1, and 2 cases, performing the corresponding node removal operations.
7 months ago
As shown in Figure 7-19, when the degree of the node to be removed is $0$, it means the node is a leaf node, and it can be directly removed.
8 months ago
![Removing a node in a binary search tree (degree 0)](binary_search_tree.assets/bst_remove_case1.png){ class="animation-figure" }
<p align="center"> Figure 7-19 &nbsp; Removing a node in a binary search tree (degree 0) </p>
7 months ago
As shown in Figure 7-20, when the degree of the node to be removed is $1$, replacing the node to be removed with its child node is sufficient.
8 months ago
![Removing a node in a binary search tree (degree 1)](binary_search_tree.assets/bst_remove_case2.png){ class="animation-figure" }
<p align="center"> Figure 7-20 &nbsp; Removing a node in a binary search tree (degree 1) </p>
7 months ago
When the degree of the node to be removed is $2$, we cannot remove it directly, but need to use a node to replace it. To maintain the property of the binary search tree "left subtree $<$ root node $<$ right subtree," **this node can be either the smallest node of the right subtree or the largest node of the left subtree**.
8 months ago
7 months ago
Assuming we choose the smallest node of the right subtree (the next node in in-order traversal), then the removal operation proceeds as shown in Figure 7-21.
8 months ago
1. Find the next node in the "in-order traversal sequence" of the node to be removed, denoted as `tmp`.
2. Replace the value of the node to be removed with `tmp`'s value, and recursively remove the node `tmp` in the tree.
=== "<1>"
![Removing a node in a binary search tree (degree 2)](binary_search_tree.assets/bst_remove_case3_step1.png){ class="animation-figure" }
=== "<2>"
![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png){ class="animation-figure" }
=== "<3>"
![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png){ class="animation-figure" }
=== "<4>"
![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png){ class="animation-figure" }
<p align="center"> Figure 7-21 &nbsp; Removing a node in a binary search tree (degree 2) </p>
The operation of removing a node also uses $O(\log n)$ time, where finding the node to be removed requires $O(\log n)$ time, and obtaining the in-order traversal successor node requires $O(\log n)$ time. Example code is as follows:
=== "Python"
```python title="binary_search_tree.py"
def remove(self, num: int):
7 months ago
"""Remove node"""
# If tree is empty, return
8 months ago
if self._root is None:
return
7 months ago
# Loop find, break after passing leaf nodes
8 months ago
cur, pre = self._root, None
while cur is not None:
7 months ago
# Found node to be removed, break loop
8 months ago
if cur.val == num:
break
pre = cur
7 months ago
# Node to be removed is in cur's right subtree
8 months ago
if cur.val < num:
cur = cur.right
7 months ago
# Node to be removed is in cur's left subtree
8 months ago
else:
cur = cur.left
7 months ago
# If no node to be removed, return
8 months ago
if cur is None:
return
7 months ago
# Number of child nodes = 0 or 1
8 months ago
if cur.left is None or cur.right is None:
7 months ago
# When the number of child nodes = 0/1, child = null/that child node
8 months ago
child = cur.left or cur.right
7 months ago
# Remove node cur
8 months ago
if cur != self._root:
if pre.left == cur:
pre.left = child
else:
pre.right = child
else:
7 months ago
# If the removed node is the root, reassign the root
8 months ago
self._root = child
7 months ago
# Number of child nodes = 2
8 months ago
else:
7 months ago
# Get the next node in in-order traversal of cur
8 months ago
tmp: TreeNode = cur.right
while tmp.left is not None:
tmp = tmp.left
7 months ago
# Recursively remove node tmp
8 months ago
self.remove(tmp.val)
7 months ago
# Replace cur with tmp
8 months ago
cur.val = tmp.val
```
=== "C++"
```cpp title="binary_search_tree.cpp"
6 months ago
/* Remove node */
void remove(int num) {
// If tree is empty, return
if (root == nullptr)
return;
TreeNode *cur = root, *pre = nullptr;
// Loop find, break after passing leaf nodes
while (cur != nullptr) {
// Found node to be removed, break loop
if (cur->val == num)
break;
pre = cur;
// Node to be removed is in cur's right subtree
if (cur->val < num)
cur = cur->right;
// Node to be removed is in cur's left subtree
else
cur = cur->left;
}
// If no node to be removed, return
if (cur == nullptr)
return;
// Number of child nodes = 0 or 1
if (cur->left == nullptr || cur->right == nullptr) {
// When the number of child nodes = 0 / 1, child = nullptr / that child node
TreeNode *child = cur->left != nullptr ? cur->left : cur->right;
// Remove node cur
if (cur != root) {
if (pre->left == cur)
pre->left = child;
else
pre->right = child;
} else {
// If the removed node is the root, reassign the root
root = child;
}
// Free memory
delete cur;
}
// Number of child nodes = 2
else {
// Get the next node in in-order traversal of cur
TreeNode *tmp = cur->right;
while (tmp->left != nullptr) {
tmp = tmp->left;
}
int tmpVal = tmp->val;
// Recursively remove node tmp
remove(tmp->val);
// Replace cur with tmp
cur->val = tmpVal;
}
}
8 months ago
```
=== "Java"
```java title="binary_search_tree.java"
7 months ago
/* Remove node */
8 months ago
void remove(int num) {
7 months ago
// If tree is empty, return
8 months ago
if (root == null)
return;
TreeNode cur = root, pre = null;
7 months ago
// Loop find, break after passing leaf nodes
8 months ago
while (cur != null) {
7 months ago
// Found node to be removed, break loop
8 months ago
if (cur.val == num)
break;
pre = cur;
7 months ago
// Node to be removed is in cur's right subtree
8 months ago
if (cur.val < num)
cur = cur.right;
7 months ago
// Node to be removed is in cur's left subtree
8 months ago
else
cur = cur.left;
}
7 months ago
// If no node to be removed, return
8 months ago
if (cur == null)
return;
7 months ago
// Number of child nodes = 0 or 1
8 months ago
if (cur.left == null || cur.right == null) {
7 months ago
// When the number of child nodes = 0/1, child = null/that child node
8 months ago
TreeNode child = cur.left != null ? cur.left : cur.right;
7 months ago
// Remove node cur
8 months ago
if (cur != root) {
if (pre.left == cur)
pre.left = child;
else
pre.right = child;
} else {
7 months ago
// If the removed node is the root, reassign the root
8 months ago
root = child;
}
}
7 months ago
// Number of child nodes = 2
8 months ago
else {
7 months ago
// Get the next node in in-order traversal of cur
8 months ago
TreeNode tmp = cur.right;
while (tmp.left != null) {
tmp = tmp.left;
}
7 months ago
// Recursively remove node tmp
8 months ago
remove(tmp.val);
7 months ago
// Replace cur with tmp
8 months ago
cur.val = tmp.val;
}
}
```
=== "C#"
```csharp title="binary_search_tree.cs"
7 months ago
[class]{BinarySearchTree}-[func]{Remove}
8 months ago
```
=== "Go"
```go title="binary_search_tree.go"
7 months ago
[class]{binarySearchTree}-[func]{remove}
8 months ago
```
=== "Swift"
```swift title="binary_search_tree.swift"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
=== "JS"
```javascript title="binary_search_tree.js"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
=== "TS"
```typescript title="binary_search_tree.ts"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
=== "Dart"
```dart title="binary_search_tree.dart"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
=== "Rust"
```rust title="binary_search_tree.rs"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
=== "C"
```c title="binary_search_tree.c"
7 months ago
[class]{BinarySearchTree}-[func]{removeItem}
8 months ago
```
=== "Kotlin"
```kotlin title="binary_search_tree.kt"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
=== "Ruby"
```ruby title="binary_search_tree.rb"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
=== "Zig"
```zig title="binary_search_tree.zig"
7 months ago
[class]{BinarySearchTree}-[func]{remove}
8 months ago
```
### 4. &nbsp; In-order traversal is ordered
7 months ago
As shown in Figure 7-22, the in-order traversal of a binary tree follows the "left $\rightarrow$ root $\rightarrow$ right" traversal order, and a binary search tree satisfies the size relationship "left child node $<$ root node $<$ right child node".
8 months ago
This means that in-order traversal in a binary search tree always traverses the next smallest node first, thus deriving an important property: **The in-order traversal sequence of a binary search tree is ascending**.
Using the ascending property of in-order traversal, obtaining ordered data in a binary search tree requires only $O(n)$ time, without the need for additional sorting operations, which is very efficient.
![In-order traversal sequence of a binary search tree](binary_search_tree.assets/bst_inorder_traversal.png){ class="animation-figure" }
<p align="center"> Figure 7-22 &nbsp; In-order traversal sequence of a binary search tree </p>
## 7.4.2 &nbsp; Efficiency of binary search trees
7 months ago
Given a set of data, we consider using an array or a binary search tree for storage. Observing Table 7-2, the operations on a binary search tree all have logarithmic time complexity, which is stable and efficient. Only in scenarios of high-frequency addition and low-frequency search and removal, arrays are more efficient than binary search trees.
8 months ago
<p align="center"> Table 7-2 &nbsp; Efficiency comparison between arrays and search trees </p>
<div class="center-table" markdown>
| | Unsorted array | Binary search tree |
| -------------- | -------------- | ------------------ |
| Search element | $O(n)$ | $O(\log n)$ |
| Insert element | $O(1)$ | $O(\log n)$ |
| Remove element | $O(n)$ | $O(\log n)$ |
</div>
In ideal conditions, the binary search tree is "balanced," thus any node can be found within $\log n$ loops.
7 months ago
However, continuously inserting and removing nodes in a binary search tree may lead to the binary tree degenerating into a chain list as shown in Figure 7-23, at which point the time complexity of various operations also degrades to $O(n)$.
8 months ago
![Degradation of a binary search tree](binary_search_tree.assets/bst_degradation.png){ class="animation-figure" }
<p align="center"> Figure 7-23 &nbsp; Degradation of a binary search tree </p>
## 7.4.3 &nbsp; Common applications of binary search trees
- Used as multi-level indexes in systems to implement efficient search, insertion, and removal operations.
- Serves as the underlying data structure for certain search algorithms.
- Used to store data streams to maintain their ordered state.