JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

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
  1. 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 の両方を子孫に持ち、それより

  2. Pythonでソート済み配列を高さバランスの二分探索木に変換する方法

    ソートされた配列 A が与えられたとき、そこから高さバランスの取れた二分探索木(BST)を生成することを考えます。ここで「高さバランスの取れた二分木」とは、すべてのノードにおいて、左右の部分木の深さの差が常に1以下であるような二分木のことを指します。例として、配列が [-10, -3, 0, 5, 9] の場合、出力の一例は [0, -3, 9, -10, null, 5] のようになります。解法のアプローチこの問題は、配列の中央要素をルートに選ぶというシンプルな発想で解くことができます。具体的な手順は以下の通りです。配列 A が空の場合は、Null(None)を返します。配列の中央の要素を見