JavaScriptで二分探索木のノードを削除する方法
木構造(ツリー)からノードを削除する処理は、一見すると複雑に感じられます。ノードの削除では、次の3つの場合分けを考慮する必要があります。これらは後述する関数のコメントにも記載しています。これまでの実装と同様に、クラス本体にメソッドを作成し、再帰的に呼び出すためのヘルパー関数を用意する形で実装していきます。
クラスメソッド
deleteNode(key) {
// ノードが正常に削除されると、参照が返されます。
return !(deleteNodeHelper(this.root, key) === false);
}
ヘルパーメソッド
削除対象のノードの状態によって、以下の3つのケースに分かれます。
ケース1:葉(リーフ)ノードの場合
子を持たないノードは、親からの接続を切断するだけで削除できます。
A
/ \
B C
/ / \
D E F
※ F を削除する場合
A
/ \
B C
/ /
D E
ケース2:子を1つだけ持つ場合
子が1つだけあるノードは、その子ノードを親ノードの位置に置き換えることで削除できます。
A
/ \
B C
/ / \
D E F
※ B を削除する場合
A
/ \
D C
/ \
E F
ケース3:両方の子を持つ場合
これは最も扱いが難しいケースです。この場合は、削除対象ノードの「後継者(successor)」または「先行者(predecessor)」を見つけ、そのノードと置き換えます。例えば後継者を採用する場合、それは対象より大きい要素のうち最小のもの、すなわち右部分木の最小要素となります。削除後の木は次のようになります。
A
/ \
B C
/ / \
D E F
/ / \
G H I
※ C を削除する場合
A
/ \
B H
/ / \
D E F
/ \
G I
この削除を実現するには、後継者の親を特定してリンクを解除し、後継者の左・右参照を現在のノードの左・右に向け直す必要があります。ただし、より簡単な方法として、削除対象ノードのデータを後継者の値で上書きし、その後継者ノードだけを削除する方法もあります。
/**
* ルートとキーを受け取り、キーを再帰的に検索します。
* キーが見つかった場合、次の3つのケースが考えられます:
*
* 1. 葉ノード → 親との接続を切るだけでよい
* 2. 子が1つ → 子ノードを親の位置に置き換える
* 3. 子が2つ → 後継者(右部分木の最小値)で置き換える
*/
function deleteNodeHelper(root, key) {
if (root === null) {
// 空の木の場合は false を返す
return false;
}
if (key < root.data) {
root.left = deleteNodeHelper(root.left, key);
return root;
} else if (key > root.data) {
root.right = deleteNodeHelper(root.right, key);
return root;
} else {
// 子がない場合
// ケース1 - 葉ノード
if (root.left === null && root.right === null) {
root = null;
return root;
}
// 子が1つの場合
if (root.left === null) return root.right;
if (root.right === null) return root.left;
// 子が2つあるため、後継者を探す必要がある
let currNode = root.right;
while (currNode.left !== null) {
currNode = currNode.left;
}
root.data = currNode.data;
// 右部分木からその値を削除する
root.right = deleteNodeHelper(root.right, currNode.data);
return root;
}
}
実行例
実際の動作は、次のコードで確認できます。
let BST = new BinarySearchTree(); BST.insertRec(10); BST.insertRec(15); BST.insertRec(5); BST.insertRec(50); BST.insertRec(3); BST.insertRec(7); BST.insertRec(12); BST.inOrder(); BST.deleteNode(15); BST.deleteNode(10); BST.deleteNode(3); BST.inOrder();
出力結果
このコードを実行すると、次のような出力が得られます。
5 7 12 50
このように、削除後も二分探索木の性質(左の子 < 親 < 右の子)が保たれていることが、昇順に出力される結果から確認できます。
-
Pythonでn分木(N-ary Tree)のルートノードを見つけるプログラム
n分木(N-ary Tree)の各ノードが配列として与えられているとします。ここで求めたいのは、木を再構築したうえでルートノードを見つけて返すことです。返されたノードを起点に、木全体を先行順(Preorder)で表示できれば成功です。 たとえば、入力が次のような場合を考えてみましょう。 このときの出力は以下のようになります。 [14, 27, 32, 42, 56, 65] この出力は、見つけたルートノードから木の先行順走査(Preorder Traversal)を行った結果です。つまり、正しいルートさえ特定できれば、そこから木全体の構造を復元できます。 解決のアプローチ:入次数(In-d
-
Pythonで二分木の左端の最深ノードを求めるプログラム
二分木が与えられたとき、最も深い位置にあるノードの値を求めることを考えます。最深ノードが複数存在する場合は、その中で最も左側にあるノードの値を返します。 例えば、次のような二分木が入力された場合を考えてみましょう。 この場合、出力は 4 になります。最も深いレベルには 4 と 7 の2つのノードがありますが、4 の方が左側に位置しているためです。 解法のアプローチ この問題は、幅優先探索(BFS)を使って木をレベルごとに走査することで効率的に解けます。各レベルの最初のノード(=そのレベルの最も左のノード)の値を記録していき、すべてのレベルの走査が完了した時点で記録されている値が、最も深いレ