This commit is contained in:
krahets
2026-04-03 18:46:15 +08:00
parent 377736b1bd
commit 9d21ca86b0
352 changed files with 46563 additions and 11262 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+186 -148
View File
@@ -4,27 +4,27 @@ comments: true
# 7.1   二分木
<u>二分木</u>は、祖先と子孫の間の階層関係を表し、「二つに分割する」分割統治法の論理を体現する非線形データ構造です。連結リストと同様に、二分木の基本単位はノードであり、各ノードは値、左子ノードへの参照、右子ノードへの参照を含みます。
<u>二分木binary tree</u>は非線形データ構造の一種であり、「祖先」と「子孫」の派生関係を表し、「一つを二つに分る」分割統治の考え方を体現しています。連結リストと同様に、二分木の基本単位はノードであり、各ノードは値、左子ノードへの参照、右子ノードへの参照を含みます。
=== "Python"
```python title=""
class TreeNode:
"""二分木ノード"""
"""二分木ノードクラス"""
def __init__(self, val: int):
self.val: int = val # ノード値
self.left: TreeNode | None = None # 左子ノードへの参照
self.right: TreeNode | None = None # 右子ノードへの参照
self.left: TreeNode | None = None # 左子ノード参照
self.right: TreeNode | None = None # 右子ノード参照
```
=== "C++"
```cpp title=""
/* 二分木ノード */
/* 二分木ノード構造体 */
struct TreeNode {
int val; // ノード値
TreeNode *left; // 左子ノードへのポインタ
TreeNode *right; // 右子ノードへのポインタ
TreeNode *left; // 左子ノードポインタ
TreeNode *right; // 右子ノードポインタ
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
```
@@ -32,11 +32,11 @@ comments: true
=== "Java"
```java title=""
/* 二分木ノード */
/* 二分木ノードクラス */
class TreeNode {
int val; // ノード値
TreeNode left; // 左子ノードへの参照
TreeNode right; // 右子ノードへの参照
TreeNode left; // 左子ノード参照
TreeNode right; // 右子ノード参照
TreeNode(int x) { val = x; }
}
```
@@ -44,18 +44,18 @@ comments: true
=== "C#"
```csharp title=""
/* 二分木ノード */
/* 二分木ノードクラス */
class TreeNode(int? x) {
public int? val = x; // ノード値
public TreeNode? left; // 左子ノードへの参照
public TreeNode? right; // 右子ノードへの参照
public TreeNode? left; // 左子ノード参照
public TreeNode? right; // 右子ノード参照
}
```
=== "Go"
```go title=""
/* 二分木ノード */
/* 二分木ノード構造体 */
type TreeNode struct {
Val int
Left *TreeNode
@@ -64,8 +64,8 @@ comments: true
/* コンストラクタ */
func NewTreeNode(v int) *TreeNode {
return &TreeNode{
Left: nil, // 左子ノードへのポインタ
Right: nil, // 右子ノードへのポインタ
Left: nil, // 左子ノードポインタ
Right: nil, // 右子ノードポインタ
Val: v, // ノード値
}
}
@@ -74,11 +74,11 @@ comments: true
=== "Swift"
```swift title=""
/* 二分木ノード */
/* 二分木ノードクラス */
class TreeNode {
var val: Int // ノード値
var left: TreeNode? // 左子ノードへの参照
var right: TreeNode? // 右子ノードへの参照
var left: TreeNode? // 左子ノード参照
var right: TreeNode? // 右子ノード参照
init(x: Int) {
val = x
@@ -89,11 +89,11 @@ comments: true
=== "JS"
```javascript title=""
/* 二分木ノード */
/* 二分木ノードクラス */
class TreeNode {
val; // ノード値
left; // 左子ノードへのポインタ
right; // 右子ノードへのポインタ
left; // 左子ノードポインタ
right; // 右子ノードポインタ
constructor(val, left, right) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
@@ -105,7 +105,7 @@ comments: true
=== "TS"
```typescript title=""
/* 二分木ノード */
/* 二分木ノードクラス */
class TreeNode {
val: number;
left: TreeNode | null;
@@ -113,8 +113,8 @@ comments: true
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val; // ノード値
this.left = left === undefined ? null : left; // 左子ノードへの参照
this.right = right === undefined ? null : right; // 右子ノードへの参照
this.left = left === undefined ? null : left; // 左子ノード参照
this.right = right === undefined ? null : right; // 右子ノード参照
}
}
```
@@ -122,11 +122,11 @@ comments: true
=== "Dart"
```dart title=""
/* 二分木ノード */
/* 二分木ノードクラス */
class TreeNode {
int val; // ノード値
TreeNode? left; // 左子ノードへの参照
TreeNode? right; // 右子ノードへの参照
TreeNode? left; // 左子ノード参照
TreeNode? right; // 右子ノード参照
TreeNode(this.val, [this.left, this.right]);
}
```
@@ -137,11 +137,11 @@ comments: true
use std::rc::Rc;
use std::cell::RefCell;
/* 二分木ノード */
/* 二分木ノード構造体 */
struct TreeNode {
val: i32, // ノード値
left: Option<Rc<RefCell<TreeNode>>>, // 左子ノードへの参照
right: Option<Rc<RefCell<TreeNode>>>, // 右子ノードへの参照
left: Option<Rc<RefCell<TreeNode>>>, // 左子ノード参照
right: Option<Rc<RefCell<TreeNode>>>, // 右子ノード参照
}
impl TreeNode {
@@ -159,12 +159,12 @@ comments: true
=== "C"
```c title=""
/* 二分木ノード */
/* 二分木ノード構造体 */
typedef struct TreeNode {
int val; // ノード値
int height; // ノードの高さ
struct TreeNode *left; // 左子ノードへのポインタ
struct TreeNode *right; // 右子ノードへのポインタ
struct TreeNode *left; // 左子ノードポインタ
struct TreeNode *right; // 右子ノードポインタ
} TreeNode;
/* コンストラクタ */
@@ -183,65 +183,74 @@ comments: true
=== "Kotlin"
```kotlin title=""
/* 二分木ノード */
/* 二分木ノードクラス */
class TreeNode(val _val: Int) { // ノード値
val left: TreeNode? = null // 左子ノードへの参照
val right: TreeNode? = null // 右子ノードへの参照
val left: TreeNode? = null // 左子ノード参照
val right: TreeNode? = null // 右子ノード参照
}
```
=== "Ruby"
```ruby title=""
### 二分木ノードクラス ###
class TreeNode
attr_accessor :val # ノード値
attr_accessor :left # 左子ノード参照
attr_accessor :right # 右子ノード参照
def initialize(val)
@val = val
end
end
```
各ノードは2つの参照(ポインタ)を持ち、それぞれ<u>左子ノード</u>と<u>右子ノード</u>を指しています。このノードはこれら2つの子ノードの<u>親ノード</u>と呼ばれます。二分木のノードが与えられたとき、のノードの左の子とその下にあるすべてのノードで形成される木を、このノードの<u>左部分木</u>と呼びます。同様に<u>右部分木</u>定義できます。
各ノードは 2 つの参照(ポインタ)を持ち、それぞれ<u>左子ノードleft-child node</u>と<u>右子ノードright-child node</u>を指します。このノードはこれら 2 つの子ノードの<u>親ノードparent node</u>と呼ばれます。二分木のあるノードが与えられたとき、のノードの左子ノードとその配下のノードからなる木をのノードの<u>左部分木left subtree</u>と呼び同様に<u>右部分木right subtree</u>定義されます。
**二分木では、葉ノードを除いて、他のすべてのノード子ノードと空でない部分木を含みます**図に示すように、「ノード2」を親ノードとして見ると、その左と右子ノードはそれぞれ「ノード4」と「ノード5」です。左部分木は「ノード4」とその下にあるすべてのノードで形成され、右部分木は「ノード5」とその下にあるすべてのノードで形成されます。
**二分木では、葉ノードを除すべてのノード子ノードと空でない部分木を持ちます**。以下の図に示すように、「ノード 2」を親ノードとみなすと、その左子ノードと右子ノードはそれぞれ「ノード 4」と「ノード 5」であり、左部分木は「ノード 4 とその配下のノードからなる木」、右部分木は「ノード 5 とその配下のノードからなる木」です。
![親ノード、子ノード、部分木](binary_tree.assets/binary_tree_definition.png){ class="animation-figure" }
<p align="center"> 図 7-1 &nbsp; 親ノード、子ノード、部分木 </p>
## 7.1.1 &nbsp; 二分木の一般的な用語
## 7.1.1 &nbsp; 二分木のよく使われる用語
二分木でよく使用される用語を図に示します。
二分木でよく使れる用語を以下の図に示します。
- <u>根ノード</u>:二分木の最上位レベルにあるノードで、親ノードを持ちません。
- <u>葉ノード</u>:子ノードを持たないノードで、両方のポインタ`None`を指しています。
- <u>辺</u>2つのノードを結ぶ線分、ノード間の参照(ポインタ)を表現します。
- ノード<u>レベル</u>:上から下向かって増加し、根ノードレベル1です。
- ノードの<u>次数</u>:ノードが持つ子ノードの数です。二分木では次数は0、1、または2になります。
- 二分木の<u>高さ</u>:根ノードから最も遠い葉ノードまで辺の数です
- ノードの<u>深さ</u>:根ノードからそのノードまで辺の数です
- ノードの<u>高さ</u>:最も遠い葉ノードからそのノードまで辺の数です
- <u>根ノードroot node</u>:二分木の最上位にあるノードで、親ノードを持ちません。
- <u>葉ノードleaf node</u>:子ノードを持たないノードで、2 本のポインタはいずれも `None` を指します。
- <u>辺edge</u>2 つのノードを結ぶ線分、すなわちノード参照(ポインタ)す。
- ノードが属する<u>レベルlevel</u>:上から下向かって増加し、根ノードレベルは 1 です。
- ノードの<u>次数degree</u>:ノード子ノードの数。二分木では次数の取り得る値は 0、1、2 です。
- 二分木の<u>高さheight</u>:根ノードから最も遠い葉ノードまでに通る辺の数。
- ノードの<u>深さdepth</u>:根ノードからそのノードまでに通る辺の数。
- ノードの<u>高さheight</u>:そのノードから最も遠い葉ノードまでに通る辺の数。
![二分木の一般的な用語](binary_tree.assets/binary_tree_terminology.png){ class="animation-figure" }
![二分木のよく使われる用語](binary_tree.assets/binary_tree_terminology.png){ class="animation-figure" }
<p align="center"> 図 7-2 &nbsp; 二分木の一般的な用語 </p>
<p align="center"> 図 7-2 &nbsp; 二分木のよく使われる用語 </p>
!!! tip
「高さ」と「深さ」は通常「通過する辺の数」として定義しますが、一部の問題や教科書では「通過するノードの数」として定義されることがあります。の場合、高さと深さの両方を1だけ増やす必要があります。
なお、通常「高さ」と「深さ」は「通過した辺の数」と定義しますが、問題や教材によっては「通過したノードの数」と定義する場合もあります。の場合、高さと深さはいずれも 1 を加える必要があります。
## 7.1.2 &nbsp; 二分木の基本操作
### 1. &nbsp; 二分木初期化
### 1. &nbsp; 二分木初期化する
連結リストと同様に、二分木の初期化では、まずノードを作成し、次にそれらの間の参照(ポインタ)を確立します。
連結リストと同様に、まずノードを初期化し、その後で参照(ポインタ)を構築します。
=== "Python"
```python title="binary_tree.py"
# 二分木初期化
# ノード初期化
# 二分木初期化する
# ノード初期化する
n1 = TreeNode(val=1)
n2 = TreeNode(val=2)
n3 = TreeNode(val=3)
n4 = TreeNode(val=4)
n5 = TreeNode(val=5)
# ノード間の参照(ポインタ)を結ぶ
# ノード間の参照(ポインタ)を構築する
n1.left = n2
n1.right = n3
n2.left = n4
@@ -251,14 +260,14 @@ comments: true
=== "C++"
```cpp title="binary_tree.cpp"
/* 二分木初期化 */
// ノード初期化
/* 二分木初期化する */
// ノード初期化する
TreeNode* n1 = new TreeNode(1);
TreeNode* n2 = new TreeNode(2);
TreeNode* n3 = new TreeNode(3);
TreeNode* n4 = new TreeNode(4);
TreeNode* n5 = new TreeNode(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1->left = n2;
n1->right = n3;
n2->left = n4;
@@ -268,13 +277,13 @@ comments: true
=== "Java"
```java title="binary_tree.java"
// ノード初期化
// ノード初期化する
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.left = n2;
n1.right = n3;
n2.left = n4;
@@ -284,14 +293,14 @@ comments: true
=== "C#"
```csharp title="binary_tree.cs"
/* 二分木初期化 */
// ノード初期化
/* 二分木初期化する */
// ノード初期化する
TreeNode n1 = new(1);
TreeNode n2 = new(2);
TreeNode n3 = new(3);
TreeNode n4 = new(4);
TreeNode n5 = new(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.left = n2;
n1.right = n3;
n2.left = n4;
@@ -301,14 +310,14 @@ comments: true
=== "Go"
```go title="binary_tree.go"
/* 二分木初期化 */
// ノード初期化
/* 二分木初期化する */
// ノード初期化する
n1 := NewTreeNode(1)
n2 := NewTreeNode(2)
n3 := NewTreeNode(3)
n4 := NewTreeNode(4)
n5 := NewTreeNode(5)
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.Left = n2
n1.Right = n3
n2.Left = n4
@@ -318,13 +327,13 @@ comments: true
=== "Swift"
```swift title="binary_tree.swift"
// ノード初期化
// ノード初期化する
let n1 = TreeNode(x: 1)
let n2 = TreeNode(x: 2)
let n3 = TreeNode(x: 3)
let n4 = TreeNode(x: 4)
let n5 = TreeNode(x: 5)
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.left = n2
n1.right = n3
n2.left = n4
@@ -334,14 +343,14 @@ comments: true
=== "JS"
```javascript title="binary_tree.js"
/* 二分木初期化 */
// ノード初期化
/* 二分木初期化する */
// ノード初期化する
let n1 = new TreeNode(1),
n2 = new TreeNode(2),
n3 = new TreeNode(3),
n4 = new TreeNode(4),
n5 = new TreeNode(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.left = n2;
n1.right = n3;
n2.left = n4;
@@ -351,14 +360,14 @@ comments: true
=== "TS"
```typescript title="binary_tree.ts"
/* 二分木初期化 */
// ノード初期化
/* 二分木初期化する */
// ノード初期化する
let n1 = new TreeNode(1),
n2 = new TreeNode(2),
n3 = new TreeNode(3),
n4 = new TreeNode(4),
n5 = new TreeNode(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.left = n2;
n1.right = n3;
n2.left = n4;
@@ -368,14 +377,14 @@ comments: true
=== "Dart"
```dart title="binary_tree.dart"
/* 二分木初期化 */
// ノード初期化
/* 二分木初期化する */
// ノード初期化する
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.left = n2;
n1.right = n3;
n2.left = n4;
@@ -385,13 +394,13 @@ comments: true
=== "Rust"
```rust title="binary_tree.rs"
// ノード初期化
// ノード初期化する
let n1 = TreeNode::new(1);
let n2 = TreeNode::new(2);
let n3 = TreeNode::new(3);
let n4 = TreeNode::new(4);
let n5 = TreeNode::new(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.borrow_mut().left = Some(n2.clone());
n1.borrow_mut().right = Some(n3);
n2.borrow_mut().left = Some(n4);
@@ -401,14 +410,14 @@ comments: true
=== "C"
```c title="binary_tree.c"
/* 二分木初期化 */
// ノード初期化
/* 二分木初期化する */
// ノード初期化する
TreeNode *n1 = newTreeNode(1);
TreeNode *n2 = newTreeNode(2);
TreeNode *n3 = newTreeNode(3);
TreeNode *n4 = newTreeNode(4);
TreeNode *n5 = newTreeNode(5);
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1->left = n2;
n1->right = n3;
n2->left = n4;
@@ -418,13 +427,13 @@ comments: true
=== "Kotlin"
```kotlin title="binary_tree.kt"
// ノード初期化
// ノード初期化する
val n1 = TreeNode(1)
val n2 = TreeNode(2)
val n3 = TreeNode(3)
val n4 = TreeNode(4)
val n5 = TreeNode(5)
// ノード間の参照(ポインタ)を結ぶ
// ノード間の参照(ポインタ)を構築する
n1.left = n2
n1.right = n3
n2.left = n4
@@ -434,26 +443,41 @@ comments: true
=== "Ruby"
```ruby title="binary_tree.rb"
# 二分木を初期化する
# ノードを初期化する
n1 = TreeNode.new(1)
n2 = TreeNode.new(2)
n3 = TreeNode.new(3)
n4 = TreeNode.new(4)
n5 = TreeNode.new(5)
# ノード間の参照(ポインタ)を構築する
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
```
??? pythontutor "実行の可視化"
https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
### 2. &nbsp; ノードの挿入と削除
連結リストと同様に、二分木でのノードの挿入と削除はポインタを変更することで実現できます。下図に例を示します。
連結リストと同様に、二分木でのノードの挿入と削除はポインタを変更することで実現できます。以下の図に 1 つの例を示します。
![二分木でノード挿入削除](binary_tree.assets/binary_tree_add_remove.png){ class="animation-figure" }
![二分木でノード挿入削除する](binary_tree.assets/binary_tree_add_remove.png){ class="animation-figure" }
<p align="center"> 図 7-3 &nbsp; 二分木でノード挿入削除 </p>
<p align="center"> 図 7-3 &nbsp; 二分木でノード挿入削除する </p>
=== "Python"
```python title="binary_tree.py"
# ノードの挿入と削除
p = TreeNode(0)
# n1 -> n2の間にノードPを挿入
# n1 -> n2 の間にノード P を挿入する
n1.left = p
p.left = n2
# ノードPを削除
# ノード P を削除する
n1.left = n2
```
@@ -462,21 +486,23 @@ comments: true
```cpp title="binary_tree.cpp"
/* ノードの挿入と削除 */
TreeNode* P = new TreeNode(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1->left = P;
P->left = n2;
// ノードPを削除
// ノード P を削除する
n1->left = n2;
// メモリを解放する
delete P;
```
=== "Java"
```java title="binary_tree.java"
TreeNode P = new TreeNode(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.left = P;
P.left = n2;
// ノードPを削除
// ノード P を削除する
n1.left = n2;
```
@@ -485,10 +511,10 @@ comments: true
```csharp title="binary_tree.cs"
/* ノードの挿入と削除 */
TreeNode P = new(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.left = P;
P.left = n2;
// ノードPを削除
// ノード P を削除する
n1.left = n2;
```
@@ -496,11 +522,11 @@ comments: true
```go title="binary_tree.go"
/* ノードの挿入と削除 */
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
p := NewTreeNode(0)
n1.Left = p
p.Left = n2
// ノードPを削除
// ノード P を削除する
n1.Left = n2
```
@@ -508,10 +534,10 @@ comments: true
```swift title="binary_tree.swift"
let P = TreeNode(x: 0)
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.left = P
P.left = n2
// ノードPを削除
// ノード P を削除する
n1.left = n2
```
@@ -520,10 +546,10 @@ comments: true
```javascript title="binary_tree.js"
/* ノードの挿入と削除 */
let P = new TreeNode(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.left = P;
P.left = n2;
// ノードPを削除
// ノード P を削除する
n1.left = n2;
```
@@ -532,10 +558,10 @@ comments: true
```typescript title="binary_tree.ts"
/* ノードの挿入と削除 */
const P = new TreeNode(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.left = P;
P.left = n2;
// ノードPを削除
// ノード P を削除する
n1.left = n2;
```
@@ -544,10 +570,10 @@ comments: true
```dart title="binary_tree.dart"
/* ノードの挿入と削除 */
TreeNode P = new TreeNode(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.left = P;
P.left = n2;
// ノードPを削除
// ノード P を削除する
n1.left = n2;
```
@@ -555,10 +581,10 @@ comments: true
```rust title="binary_tree.rs"
let p = TreeNode::new(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.borrow_mut().left = Some(p.clone());
p.borrow_mut().left = Some(n2.clone());
// ノードPを削除
// ノード p を削除する
n1.borrow_mut().left = Some(n2);
```
@@ -567,67 +593,79 @@ comments: true
```c title="binary_tree.c"
/* ノードの挿入と削除 */
TreeNode *P = newTreeNode(0);
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1->left = P;
P->left = n2;
// ノードPを削除
// ノード P を削除する
n1->left = n2;
// メモリを解放する
free(P);
```
=== "Kotlin"
```kotlin title="binary_tree.kt"
val P = TreeNode(0)
// n1n2の間にノードPを挿入
// n1 -> n2 の間にノード P を挿入する
n1.left = P
P.left = n2
// ノードPを削除
// ノード P を削除する
n1.left = n2
```
=== "Ruby"
```ruby title="binary_tree.rb"
# ノードの挿入と削除
_p = TreeNode.new(0)
# n1 -> n2 の間にノード _p を挿入する
n1.left = _p
_p.left = n2
# ノードを削除する
n1.left = n2
```
!!! tip
??? pythontutor "実行の可視化"
ノードの挿入は二分木の元の論理構造を変更する可能性があり、ノードの削除は通常そのノードとそのすべての部分木を削除することになることに注意してください。したがって、二分木では、挿入と削除は通常一連の操作を通じて実行され、意味のある結果を得ます。
## 7.1.3 &nbsp; 二分木の一般的な種類
### 1. &nbsp; 完全二分木
下図に示すように、<u>完全二分木</u>では、すべてのレベルがノードで完全に埋められています。完全二分木では、葉ノードの次数は$0$で、他のすべてのノードの次数は$2$です。ノードの総数は$2^{h+1} - 1$として計算でき、ここで$h$は木の高さです。これは標準的な指数関係を示し、自然界の細胞分裂の一般的な現象を反映しています。
https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
!!! tip
中国語圏では、完全二分木はしばしば<u>満二分木</u>と呼ばれることに注意してください
注意すべき点として、ノードの挿入は二分木の元の論理構造を変える可能性があり、ノードの削除は通常、そのノードと配下のすべての部分木の削除を意味します。そのため、二分木における挿入と削除は、実際に意味のある操作を実現するために、通常は一連の操作を組み合わせて行います
![完全二分木](binary_tree.assets/perfect_binary_tree.png){ class="animation-figure" }
## 7.1.3 &nbsp; 一般的な二分木の種類
<p align="center"> 図 7-4 &nbsp; 完全二分木 </p>
### 1. &nbsp; 充足二分木
### 2. &nbsp; 完備二分木
以下の図に示すように、<u>充足二分木(perfect binary tree</u>ではすべてのレベルのノードが完全に埋まっています。充足二分木では、葉ノードの次数は $0$ で、それ以外のすべてのノードの次数は $2$ です。木の高さを $h$ とすると、ノード総数は $2^{h+1} - 1$ となり、標準的な指数関係を示して、自然界でよく見られる細胞分裂の現象を反映しています。
下図に示すように、<u>完備二分木</u>は、最下位レベルのみが完全に埋められていない可能性がある二分木で、最下位レベルのノードは左から右に連続して埋められる必要があります。完全二分木は完備二分木でもあることに注意してください。
!!! tip
![完備二分木](binary_tree.assets/complete_binary_tree.png){ class="animation-figure" }
なお、中国語圏では充足二分木は<u>満二分木</u>と呼ばれることもあります。
<p align="center"> 図 7-5 &nbsp; 完備二分木 </p>
![充足二分木](binary_tree.assets/perfect_binary_tree.png){ class="animation-figure" }
### 3. &nbsp; 二分木
<p align="center"> 図 7-4 &nbsp; 充足二分木 </p>
下図に示すように、<u>満二分木</u>では、葉ノードを除いて、他のすべてのノードが2つの子ノードを持ちます。
### 2. &nbsp; 完全二分木
![満二分木](binary_tree.assets/full_binary_tree.png){ class="animation-figure" }
以下の図に示すように、<u>完全二分木(complete binary tree)</u>では最下層のノードだけが完全に埋まっていなくてもよく、しかも最下層のノードは左から右へ連続して詰められていなければなりません。なお、充足二分木も完全二分木の一種です。
<p align="center"> 図 7-6 &nbsp; 満二分木 </p>
![完全二分木](binary_tree.assets/complete_binary_tree.png){ class="animation-figure" }
<p align="center"> 図 7-5 &nbsp; 完全二分木 </p>
### 3. &nbsp; 充満二分木
以下の図に示すように、<u>充満二分木(full binary tree</u>では、葉ノードを除くすべてのノードが 2 つの子ノードを持ちます。
![充満二分木](binary_tree.assets/full_binary_tree.png){ class="animation-figure" }
<p align="center"> 図 7-6 &nbsp; 充満二分木 </p>
### 4. &nbsp; 平衡二分木
図に示すように、<u>平衡二分木</u>では、任意のノードの左と右部分木の高さの絶対差が1を超えません。
以下の図に示すように、<u>平衡二分木balanced binary tree</u>では、任意のノードについて左部分木と右部分木の高さの差の絶対値が 1 を超えません。
![平衡二分木](binary_tree.assets/balanced_binary_tree.png){ class="animation-figure" }
@@ -635,26 +673,26 @@ comments: true
## 7.1.4 &nbsp; 二分木の退化
図は、二分木の理想的な構造と退化した構造を示しています。二分木は、すべてのレベルが埋められているときに「完全二分木」なり、すべてのノードが一方に偏っているときに「連結リスト」退化します。
以下の図は、二分木の理想的な構造と退化した構造を示しています。二分木の各レベルのノードがすべて埋まっていると「充足二分木」なり、すべてのノードが片側に偏ると二分木は「連結リスト」退化します。
- 完全二分木は、二分木の「分割統治」の利点を十分に活用できる理想的なシナリオです。
- 一方、連結リストは別の極端を表し、すべての操作が線形になり、時間計算量$O(n)$になります。
- 充足二分木は理想的なケースであり、二分木の「分割統治」の利点を十分に発揮できます。
- 連結リストはその対極にあり、各種操作はすべて線形操作となり、時間計算量$O(n)$ まで退化します。
![二分木の最良と最悪構造](binary_tree.assets/binary_tree_best_worst_cases.png){ class="animation-figure" }
![二分木の最良構造と最悪構造](binary_tree.assets/binary_tree_best_worst_cases.png){ class="animation-figure" }
<p align="center"> 図 7-8 &nbsp; 二分木の最良と最悪構造 </p>
<p align="center"> 図 7-8 &nbsp; 二分木の最良構造と最悪構造 </p>
表に示すように、最良と最悪構造では、二分木葉ノード数、ノード数、高さの最大値または最小値を達成します。
以下の表に示すように、最良構造と最悪構造では、二分木葉ノード数、ノード数、高さなどが極大または極小になります。
<p align="center"> 表 7-1 &nbsp; 二分木の最良と最悪構造 </p>
<p align="center"> 表 7-1 &nbsp; 二分木の最良構造と最悪構造 </p>
<div class="center-table" markdown>
| | 完全二分木 | 連結リスト |
| ----------------------------------------------- | ------------------ | ----------- |
| レベル$i$のノード数 | $2^{i-1}$ | $1$ |
| 高さ$h$の木の葉ノード数 | $2^h$ | $1$ |
| 高さ$h$の木のノード数 | $2^{h+1} - 1$ | $h + 1$ |
| ノード$n$の木の高さ | $\log_2 (n+1) - 1$ | $n - 1$ |
| | 充足二分木 | 連結リスト |
| --------------------------- | ------------------ | ------- |
| 第 $i$ レベルのノード数 | $2^{i-1}$ | $1$ |
| 高さ $h$ の木の葉ノード数 | $2^h$ | $1$ |
| 高さ $h$ の木のノード数 | $2^{h+1} - 1$ | $h + 1$ |
| ノード総数 $n$ の木の高さ | $\log_2 (n+1) - 1$ | $n - 1$ |
</div>
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -9,9 +9,9 @@ icon: material/graph-outline
!!! abstract
そびえ立つ木は活力に満ちた本質を放ち、深い根と豊かな葉を誇りながらも、その枝は疎らに散らばり、幽玄な雰囲気を醸し出しています
それはデータにおける分割統治の鮮やかな形を私たちに示しています
大樹は生命力に満ち、根は深く葉は生い茂り、枝は豊かに広がる
それはデータ分割統治の生き生きとした姿を私たちに示してくれる
## 章の内容
@@ -19,5 +19,5 @@ icon: material/graph-outline
- [7.2 &nbsp; 二分木の走査](binary_tree_traversal.md)
- [7.3 &nbsp; 二分木の配列表現](array_representation_of_tree.md)
- [7.4 &nbsp; 二分探索木](binary_search_tree.md)
- [7.5 &nbsp; AVL木 *](avl_tree.md)
- [7.5 &nbsp; AVL 木 *](avl_tree.md)
- [7.6 &nbsp; まとめ](summary.md)
+31 -31
View File
@@ -4,55 +4,55 @@ comments: true
# 7.6 &nbsp; まとめ
### 1. &nbsp; 重要なポイント
### 1. &nbsp; 要点の振り返り
- 二分木は非線形データ構造で、「一つを二つに分ける」分割統治のロジックを反映しています。各二分木ノードには値と2つのポインタが含まれ、それぞれ左と右子ノードを指します。
- 二分木のノードについて、その左(右)子ノードとその下に形成される木は、まとめてそのノードの左(右)部分木と呼ばれます
- 二分木に関する用語には、根ノード、葉ノード、レベル、次数、エッジ、高さ、深さがあります
- 二分木の初期化、ノードの挿入、ノードの削除の操作は、連結リストの操作と似ています
- 一般的な二分木の種類には、完全二分木、完備二分木、満二分木、平衡二分木があります。完全二分木は理想的な状態を表し、連結リストは退化後の最悪の状態で
- 二分木は、ノード値と空きスロットをレベル順走査シーケンスで配置し、親ノードと子ノードのインデックスマッピング関係に基づいてポインタを実することで、配列を使用して表現できます
- 二分木のレベル順走査は幅優先探索手法で、「円を拡大しながら」の層ごとの走査方式を反映しています。通常はキューを使用して実装されます
- 前順、中順、後順走査はすべて深度優先探索手法で、「まず最後まで行き、その後バックトラックして続行する」走査方式を反映しています。通常は再帰を使用して実装されます
- 二分探索木は要素検索のための効率的なデータ構造で、検索、挿入、削除操作の時間計算量はすべて$O(\log n)$です。二分探索木が連結リスト退化すると、これらの時間計算量は$O(n)$に悪化します
- AVL木は平衡二分探索木とも呼ばれ、回転操作を通して継続的なノード挿入と削除後も木が平衡を保つことを保証します
- AVL木の回転操作には、右回転、左回転、右左回転、左右回転があります。ノードの挿入または削除後、AVL木はボトムアップ方式でこれらの回転を実行して自己平衡を取ります。
- 二分木は非線形データ構造の一種であり、「二分する」分割統治の考え方を体現してい。各二分木ノードは 1 つの値と 2 本のポインタを持ち、それぞれ左子ノードと右子ノードを指す。
- 二分木のあるノードについて、その左(右)子ノードおよびその配下から構成される木を、そのノードの左(右)部分木と呼
- 二分木に関する用語には、根ノード、葉ノード、レベル、次数、、高さ、深さなどがある
- 二分木の初期化、ノードの挿入、ノードの削除は、連結リストの操作方法と似てい
- 一般的な二分木の種類には、perfect 二分木、complete 二分木、full 二分木、平衡二分木がある。perfect 二分木が最も理想的な状態であり、連結リストは退化後の最悪の状態である
- 二分木は配列で表現できる。方法としては、ノード値と空き位置をレベル順走査の順に並べ、親ノードと子ノードのインデックス対応関係に基づいてポインタを実する。
- 二分木のレベル順走査は幅優先探索の一種であり、「同心円状に外へ広がる」ような逐次的な走査方式を表しており、通常はキューによって実装され
- 前順、中順、後順走査はいずれも深さ優先探索に属し、「まず末端まで進み、その後バックトラックして続ける」という走査方式を体現しており、通常は再帰で実装され
- 二分探索木は効率的な要素探索データ構造であり、探索、挿入、削除の時間計算量はいずれも $O(\log n)$ である。二分探索木が連結リスト退化すると、各操作の時間計算量は $O(n)$ まで悪化する
- AVL 木は平衡二分探索木とも呼ばれ、回転操作によって、ノード挿入と削除を繰り返した後も木が平衡を保つようにしている
- AVL 木の回転操作には、右回転、左回転、右回転してから左回転、左回転してから右回転があ。ノードの挿入または削除後、AVL 木は下から上へ回転操作を行い、木を再び平衡状態に戻す。
### 2. &nbsp; Q & A
**Q**: 一つのノードのみを持つ二分木について、木の高さと根ノードの深さの両方が$0$ですか?
**Q**:ノードが 1 つしかない二分木では、木の高さと根ノードの深さはどちらも $0$ ですか?
はい高さと深さは通常「通過したエッジの数」として定義されるためです。
はい高さと深さは通常「通過した辺の本数」として定義されるからです。
**Q**: 二分木における挿入と削除は一般的に一連の操作によって達成されます。ここで「一連の操作」とは何を指しますか?子ノードのリソース解放することを意味しますか?
**Q**二分木における挿入と削除は通常一連の操作を組み合わせて完了しますが、ここでいう「一連の操作」とは何を指すのでしょうか?リソースの子ノードに対するリソース解放と理解できますか?
二分探索木を例にると、ノード削除する操作は3つの異なるシナリオで処理する必要があり、それぞれ複数ステップのノード操作が必要す。
二分探索木を例にると、ノード削除は 3 つのケースに分けて処理する必要があり、各ケースで複数段階のノード操作が必要になります。
**Q**: 二分木のDFS走査で前順中順後順の3つのシーケンスがあるのはなぜですか?その用途は何ですか?
**Q**:なぜ DFS による二分木走査には前順中順後順の 3 種類があり、それぞれどのような用途があるのですか?
配列の順次および逆順走査と同様に、前順中順後順走査は二分木を走査する3つの方法であり、特定の順序で走査結果を取得できます。えば二分探索木では、ノードサイズが「左子ノード値 < 根ノード値 < 右子ノード値」を満たすため、「左 $\rightarrow$ 根 $\rightarrow$ 右」の優先順で木を走査することで、順序付けられたノードシーケンスを取得できます。
配列の順方向走査と逆方向走査に似て、前順中順後順走査は二分木の 3 つの走査方法であり、特定の順序で走査結果を得るために使えます。たとえば二分探索木では、ノードの大小関係が `左子ノード値 < 根ノード値 < 右子ノードの値` を満たすため、「左 $\rightarrow$ 根 $\rightarrow$ 右」の優先順で木を走査すれば、整列済みのノード列を得られます。
**Q**: 不平衡ノード`node``child``grand_child`の関係を処理する右回転操作において、右回転後に`node`とその親ノード間の接続`node`の元のリンクが失われるのではありませんか?
**Q**:右回転操作は不平衡ノード `node``child``grand_child` の関係を処理するものですが、`node` の親ノードと `node` の元の接続は維持しなくてよいのですか?右回転後に切れてしまいませんか?
この問題再帰的な観点からる必要があります。`right_rotate(root)`操作は部分木の根ノードを渡し、最終的に`return child`で回転された部分木の根ノードを返します。部分木の根ノードとその親ノードの接続は、この関数が戻った後に確立され、これは右回転操作の保守範囲外です
この問題再帰の視点から考える必要があります。右回転操作 `right_rotate(root)` に渡されるのは部分木の根ノードであり、最終的に `return child` によって回転後の部分木の根ノードを返します。部分木の根ノードとその親ノードの接続は、この関数の返却後に行われるため、右回転操作自身が管理する範囲には含まれません
**Q**: C++では関数`private``public`セクションに分かれています。これにはどのような考慮事項がありますか?なぜ`height()`関数と`updateHeight()`関数それぞれ`public``private`に配置されているのですか?
**Q**C++ では関数`private``public` に分けますが、この設計にはどのような考がありますか?なぜ `height()` 関数と `updateHeight()` 関数それぞれ `public``private` に置くのですか?
これはメソッドの使用範囲によります。メソッドがクラス内でのみ使用される場合`private`に設計されます。えば、ユーザーが独自に`updateHeight()`を呼び出すことは意味がありません。これは挿入または削除操作の一ステップに過ぎないからです。しかし、`height()`はノードの高さにアクセスするためのもので、`vector.size()`と同様であるため、使用のために`public`に設定されています。
主に、そのメソッドの用範囲を見て決めます。メソッドがクラス内部でしか使われないなら`private` に設計ます。たとえば、利用者が `updateHeight()` を単独で呼び出しても意味はなく、これは挿入や削除の途中の 1 ステップにすぎません。一方で `height()` はノードの高さにアクセスするためのもので、`vector.size()` に似た役割を持つため、使いやすいように `public` に設定ます。
**Q**: 入力データのセットから二分探索木をどのように構築しますか?根ノードの選択は非常に重要ですか?
**Q**入力データの集合から二分探索木をどのように構築しますか?根ノードの選び方は重要ですか?
はい、木を構築する方法は二分探索木コード`build_tree()`メソッドで提供されています。根ノードの選択については、通常入力データをソートし、中央の要素を根ノードして選択し、再帰的に左と右の部分木を構築します。このアプローチは木の平衡を最大化します。
はい。木の構築方法は二分探索木コード中の `build_tree()` メソッドですでに示されています。根ノードの選択については、通常入力データをソートし、その中央の要素を根ノードしてから、左右の部分木を再帰的に構築します。こうすることで、木の平衡を最大限に保てます。
**Q**: Javaでは、文字列比較に常に`equals()`メソッドを使用する必要がありますか?
**Q**Java では、文字列比較には必ず `equals()` メソッドを使うべきですか?
Javaでは、プリミティブデータ型の場合、`==`は2つの変数の値が等しいかどうかを比較するために使用されます。参照型の場合、2つのシンボルの動作原理は異なります。
Java では、基本データ型については `==` を使って 2 つの変数の値が等しいかどうかを比較します。参照型については、この 2 つの記法の働き方は異なります。
- `==`: 2つの変数が同じオブジェクトを指しているかどうか、つまりメモリの位置が同じかどうかを比較するために使用されます。
- `equals()`: 2つのオブジェクトの値が等しいかどうかを比較するために使用されます。
- `==` 2 つの変数が同じオブジェクトを指しているか、つまりメモリの位置が同じかどうかを比較するために使ます。
- `equals()`2 つのオブジェクトの値が等しいかどうかを比較するために使ます。
したがって、値を比較するには`equals()`を使用すべきです。ただし、`String a = "hi"; String b = "hi";`初期化された文字列は文字列定数プールに格納され、同じオブジェクトを指すため、`a == b`も2つの文字列の内容を比較するために使用できます。
したがって、値を比較したい場合は `equals()` を使べきです。ただし、`String a = "hi"; String b = "hi";` によって初期化された文字列は文字列定数プールに格納され、同じオブジェクトを指すため、`a == b` でも 2 つの文字列の内容を比較できます。
**Q**: 最下位レベルに到達する前に、幅優先走査でキュー内のノード数は$2^h$ですか?
**Q**:幅優先走査で最下層に到達する前、キュー内のノード数は $2^h$ ですか?
はい、例えば高さ$h = 2$の満二分木は合計$n = 7$個のノードを持ち、最下位レベルには$4 = 2^h = (n + 1) / 2$個のノードがあります。
はい。たとえば高さ $h = 2$ の充足二分木では、ノード総数は $n = 7$ であり、最下層のノード数は $4 = 2^h = (n + 1) / 2$す。