JavaScriptで二分探索木(BST)から目的のノードを削除する方法
本記事では、JavaScriptで実装した二分探索木(Binary Search Tree:BST)から、指定した値を持つノードを削除する方法を解説します。
問題
まず、以下のコードを見てください。このコードは二分探索木のデータ構造を生成し、ノードを挿入する機能を提供します。
class Node{
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
};
};
class BinarySearchTree{
constructor(){
// 二分探索木のルート
this.root = null;
}
insert(data){
var newNode = new Node(data);
if(this.root === null){
this.root = newNode;
}else{
this.insertNode(this.root, newNode);
};
};
insertNode(node, newNode){
if(newNode.data < node.data){
if(node.left === null){
node.left = newNode;
}else{
this.insertNode(node.left, newNode);
};
} else {
if(node.right === null){
node.right = newNode;
}else{
this.insertNode(node.right,newNode);
};
};
};
};
const BST = new BinarySearchTree();
BST.insert(5);
BST.insert(3);
BST.insert(6);
BST.insert(2);
BST.insert(4);
BST.insert(7);
このコードを実行すると、BSTは次のような構造になります。
5 / \ 3 6 / \ \ 2 4 7
ここで、任意のBSTのルートを第1引数に、数値を第2引数として受け取る deleteNode() 関数を新たに実装する必要があります。
第2引数で指定された値がツリー内に存在する場合は、その値を持つノードを削除します。存在しない場合は何も行いません。どちらの場合でも、関数は更新後のBSTのルートを返す必要があります。
実装例
コードは以下のようになります。
class Node{
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
};
};
class BinarySearchTree{
constructor(){
// 二分探索木のルート
this.root = null;
}
insert(data){
var newNode = new Node(data);
if(this.root === null){
this.root = newNode;
}else{
this.insertNode(this.root, newNode);
};
};
insertNode(node, newNode){
if(newNode.data < node.data){
if(node.left === null){
node.left = newNode;
}else{
this.insertNode(node.left, newNode);
};
} else {
if(node.right === null){
node.right = newNode;
}else{
this.insertNode(node.right,newNode);
};
};
};
};
const BST = new BinarySearchTree();
BST.insert(5);
BST.insert(3);
BST.insert(6);
BST.insert(2);
BST.insert(4);
BST.insert(7);
const printTree = (node) => {
if(node !== null) {
printTree(node.left);
console.log(node.data);
printTree(node.right);
};
};
const deleteNode = function(root, key) {
if(!root){
return null;
};
if(root.data > key){
if(!root.left){
return root;
}else{
root.left = deleteNode(root.left, key);
};
} else if(root.data < key){
if(!root.right) return root;
else root.right = deleteNode(root.right, key);
} else {
if(!root.left || !root.right){
return root.left || root.right;
} else {
let nd = new Node();
let right = root.right;
nd.left = root.left;
while(right.left){
right = right.left;
}
nd.data = right.data;
nd.right = deleteNode(root.right, right.data);
return nd;
}
}
return root;
};
console.log('ノードを削除する前');
printTree(BST.root);
console.log('データが4のノードを削除した後');
printTree(deleteNode(BST.root, 4));
コードの解説
対象となるノードが見つかったときに考慮すべきケースは、合計で3つあります。
葉ノードである(左にも右にも子がない)
左の子のみを持つ、または右の子のみを持つ
左右両方の子を持つ
1と2のケースは簡単です。nullを返すか、持っている子(左または右)をそのまま返すだけで対応できます。
一方、最後のケースでは、対象ノードを削除した後にどのノードがその位置を引き継ぐのかを考える必要があります。単純に左の子や右の子を持ち上げてくると、BSTの性質(左側の子孫は親より小さく、右側の子孫は親より大きい)が崩れてしまいます。そこで、「右部分木の中の最小値」か「左部分木の中の最大値」のどちらかを探してきて置き換える必要があります。今回の実装では、右部分木の最小値(中間順走査における次のノード、いわゆる後継ノード)を採用しています。
なお、このアルゴリズムの計算量は木の高さを h とすると O(h) となり、バランスの取れたBSTであれば O(log n) で効率的に動作します。
出力結果
コンソールには次のように出力されます。
ノードを削除する前 2 3 4 5 6 7 データが4のノードを削除した後 2 3 5 6 7
-
Pythonで二分探索木の最小共通祖先(LCA)を求める方法【再帰アルゴリズム解説】
最小共通祖先(LCA)とは二分探索木(Binary Search Tree)が与えられたとき、指定された2つのノードの最小共通祖先(Lowest Common Ancestor:LCA)を求める問題を考えてみましょう。ノード p と q の LCA とは、「p と q の両方を子孫として持つノードの中で、最も深い位置にあるノード」のことです。例えば、次のような二分木があるとします。[6, 2, 8, 0, 4, 7, 9, null, null, 3, 5]この木は以下のような構造になります。この場合、2 と 8 の LCA は 6 となります。6 は 2 と 8 の両方を子孫に持ち、それより
-
Pythonでソート済み配列を高さバランスの二分探索木に変換する方法
ソートされた配列 A が与えられたとき、そこから高さバランスの取れた二分探索木(BST)を生成することを考えます。ここで「高さバランスの取れた二分木」とは、すべてのノードにおいて、左右の部分木の深さの差が常に1以下であるような二分木のことを指します。例として、配列が [-10, -3, 0, 5, 9] の場合、出力の一例は [0, -3, 9, -10, null, 5] のようになります。解法のアプローチこの問題は、配列の中央要素をルートに選ぶというシンプルな発想で解くことができます。具体的な手順は以下の通りです。配列 A が空の場合は、Null(None)を返します。配列の中央の要素を見