図 7-1 親ノード、子ノード、部分木
## 7.1.1 二分木の一般的な用語 二分木でよく使用される用語を下図に示します。 - 根ノード:二分木の最上位レベルにあるノードで、親ノードを持ちません。 - 葉ノード:子ノードを持たないノードで、両方のポインタが`None`を指しています。 - 辺:2つのノードを結ぶ線分で、ノード間の参照(ポインタ)を表現します。 - ノードのレベル:上から下に向かって増加し、根ノードがレベル1です。 - ノードの次数:ノードが持つ子ノードの数です。二分木では、次数は0、1、または2になります。 - 二分木の高さ:根ノードから最も遠い葉ノードまでの辺の数です。 - ノードの深さ:根ノードからそのノードまでの辺の数です。 - ノードの高さ:最も遠い葉ノードからそのノードまでの辺の数です。 { class="animation-figure" }図 7-2 二分木の一般的な用語
!!! tip 「高さ」と「深さ」は通常「通過する辺の数」として定義しますが、一部の問題や教科書では「通過するノードの数」として定義されることがあります。この場合、高さと深さの両方を1だけ増やす必要があります。 ## 7.1.2 二分木の基本操作 ### 1. 二分木の初期化 連結リストと同様に、二分木の初期化では、まずノードを作成し、次にそれらの間の参照(ポインタ)を確立します。 === "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 n2.right = n5 ``` === "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; n2->right = n5; ``` === "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; n2.right = n5; ``` === "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; n2.right = n5; ``` === "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 n2.Right = n5 ``` === "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 n2.right = n5 ``` === "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; n2.right = n5; ``` === "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; n2.right = n5; ``` === "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; n2.right = n5; ``` === "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); n2.borrow_mut().right = Some(n5); ``` === "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; n2->right = n5; ``` === "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 n2.right = n5 ``` === "Ruby" ```ruby title="binary_tree.rb" ``` ### 2. ノードの挿入と削除 連結リストと同様に、二分木でのノードの挿入と削除はポインタを変更することで実現できます。下図に例を示します。 { class="animation-figure" }図 7-3 二分木でのノードの挿入と削除
=== "Python" ```python title="binary_tree.py" # ノードの挿入と削除 p = TreeNode(0) # n1 -> n2の間にノードPを挿入 n1.left = p p.left = n2 # ノードPを削除 n1.left = n2 ``` === "C++" ```cpp title="binary_tree.cpp" /* ノードの挿入と削除 */ TreeNode* P = new TreeNode(0); // n1とn2の間にノードPを挿入 n1->left = P; P->left = n2; // ノードPを削除 n1->left = n2; ``` === "Java" ```java title="binary_tree.java" TreeNode P = new TreeNode(0); // n1とn2の間にノードPを挿入 n1.left = P; P.left = n2; // ノードPを削除 n1.left = n2; ``` === "C#" ```csharp title="binary_tree.cs" /* ノードの挿入と削除 */ TreeNode P = new(0); // n1とn2の間にノードPを挿入 n1.left = P; P.left = n2; // ノードPを削除 n1.left = n2; ``` === "Go" ```go title="binary_tree.go" /* ノードの挿入と削除 */ // n1とn2の間にノードPを挿入 p := NewTreeNode(0) n1.Left = p p.Left = n2 // ノードPを削除 n1.Left = n2 ``` === "Swift" ```swift title="binary_tree.swift" let P = TreeNode(x: 0) // n1とn2の間にノードPを挿入 n1.left = P P.left = n2 // ノードPを削除 n1.left = n2 ``` === "JS" ```javascript title="binary_tree.js" /* ノードの挿入と削除 */ let P = new TreeNode(0); // n1とn2の間にノードPを挿入 n1.left = P; P.left = n2; // ノードPを削除 n1.left = n2; ``` === "TS" ```typescript title="binary_tree.ts" /* ノードの挿入と削除 */ const P = new TreeNode(0); // n1とn2の間にノードPを挿入 n1.left = P; P.left = n2; // ノードPを削除 n1.left = n2; ``` === "Dart" ```dart title="binary_tree.dart" /* ノードの挿入と削除 */ TreeNode P = new TreeNode(0); // n1とn2の間にノードPを挿入 n1.left = P; P.left = n2; // ノードPを削除 n1.left = n2; ``` === "Rust" ```rust title="binary_tree.rs" let p = TreeNode::new(0); // n1とn2の間にノードPを挿入 n1.borrow_mut().left = Some(p.clone()); p.borrow_mut().left = Some(n2.clone()); // ノードPを削除 n1.borrow_mut().left = Some(n2); ``` === "C" ```c title="binary_tree.c" /* ノードの挿入と削除 */ TreeNode *P = newTreeNode(0); // n1とn2の間にノードPを挿入 n1->left = P; P->left = n2; // ノードPを削除 n1->left = n2; ``` === "Kotlin" ```kotlin title="binary_tree.kt" val P = TreeNode(0) // n1とn2の間にノードPを挿入 n1.left = P P.left = n2 // ノードPを削除 n1.left = n2 ``` === "Ruby" ```ruby title="binary_tree.rb" ``` !!! tip ノードの挿入は二分木の元の論理構造を変更する可能性があり、ノードの削除は通常そのノードとそのすべての部分木を削除することになることに注意してください。したがって、二分木では、挿入と削除は通常一連の操作を通じて実行され、意味のある結果を得ます。 ## 7.1.3 二分木の一般的な種類 ### 1. 完全二分木 下図に示すように、完全二分木では、すべてのレベルがノードで完全に埋められています。完全二分木では、葉ノードの次数は$0$で、他のすべてのノードの次数は$2$です。ノードの総数は$2^{h+1} - 1$として計算でき、ここで$h$は木の高さです。これは標準的な指数関係を示し、自然界の細胞分裂の一般的な現象を反映しています。 !!! tip 中国語圏では、完全二分木はしばしば満二分木と呼ばれることに注意してください。 { class="animation-figure" }図 7-4 完全二分木
### 2. 完備二分木 下図に示すように、完備二分木は、最下位レベルのみが完全に埋められていない可能性がある二分木で、最下位レベルのノードは左から右に連続して埋められる必要があります。完全二分木は完備二分木でもあることに注意してください。 { class="animation-figure" }図 7-5 完備二分木
### 3. 満二分木 下図に示すように、満二分木では、葉ノードを除いて、他のすべてのノードが2つの子ノードを持ちます。 { class="animation-figure" }図 7-6 満二分木
### 4. 平衡二分木 下図に示すように、平衡二分木では、任意のノードの左と右の部分木の高さの絶対差が1を超えません。 { class="animation-figure" }図 7-7 平衡二分木
## 7.1.4 二分木の退化 下図は、二分木の理想的な構造と退化した構造を示しています。二分木は、すべてのレベルが埋められているときに「完全二分木」になり、すべてのノードが一方に偏っているときに「連結リスト」に退化します。 - 完全二分木は、二分木の「分割統治法」の利点を十分に活用できる理想的なシナリオです。 - 一方、連結リストは別の極端を表し、すべての操作が線形になり、時間計算量が$O(n)$になります。 { class="animation-figure" }図 7-8 二分木の最良と最悪の構造
下表に示すように、最良と最悪の構造では、二分木は葉ノード数、総ノード数、高さの最大値または最小値を達成します。表 7-1 二分木の最良と最悪の構造