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

C++で二分木のノードを削除する方法|最深ノードとの置換アルゴリズム

二分木における削除処理の概要

二分木からのノード削除は、削除したいノードを「木の中で最も深い位置にある右端のノード」で置き換えることによって行われます。この手法では、まず削除対象ノードの値を最深ノードの値で上書きし、その後、最深ノードを木から切り離してメモリを解放します。

まず、データ本体と左右の子ノードへのポインタを保持する、ツリーノードを表す構造体を定義します。最初に作成されたノードはルートノードとなり、それ以降に作成されたものは子ノードとして扱われます。

struct Node {
   int data;
   struct Node *leftChild, *rightChild;
};

新しいノードを作成する

次に、int型の値を受け取り、その値をノードのdataメンバに代入するnewNode(int data)関数を作成します。この関数は生成したNode構造体へのポインタを返します。また、新しく作成されたノードの左右の子ポインタはNULLに初期化されます。

struct Node* newNode(int data){
   struct Node* newNode = new Node;
   newNode->data = data;
   newNode->leftChild = newNode->rightChild = NULL;
   return (newNode);
}

削除関数deletion()の実装

deletion(struct Node* root, int data)関数は、指定されたデータ値を持つノードを削除するために使用されます。引数としてルートノードと、検索・削除対象となるデータ値を受け取ります。もし子ノードが存在せず、かつデータ値がルートのデータ値と一致する場合はNULLを返し、一致しない場合はそのままルートノードを返します。

Node* deletion(struct Node* root, int data){
   if (root == NULL)
      return NULL;
   if (root->leftChild == NULL && root->rightChild == NULL) {
      if (root->data == data)
         return NULL;
      else
         return root;
   }

続いて、struct Node* 型のキューqを作成し、そこにルートノードをプッシュします。また、Nodeへのポインタであるtempとdata_nodeを宣言し、data_nodeはNULLで初期化しておきます。

struct Node* temp;
struct Node* data_node = NULL;

次に、レベル順走査(幅優先探索)を行って最も深いノードを見つけます。whileループはキューqが空になるまで実行されます。キューはFIFO(先入れ先出し)のデータ構造なので、レベル順に走査していけば、キューの最後の要素が最も深い位置にある右端のノードとなります。tempは常にキューの先頭を指し、要素は前から順番にポップされていきます。

while (!q.empty()) {
   temp = q.front();
   q.pop();
   if (temp->data == data)
      data_node = temp;
   if (temp->leftChild)
      q.push(temp->leftChild);
   if (temp->rightChild)
      q.push(temp->rightChild);
}

その後、data_nodeがNULLでない場合、削除対象ノードのデータをxに保存し、tempが指す最深ノードを削除します。data_nodeの値は最深ノードの値で置き換えられ、最深ノード自体は削除されます。削除と置き換えが完了すると、更新されたルートノードが関数から返されます。

if (data_node != NULL) {
   int x = temp->data;
   deleteDeepest(root, temp);
   data_node->data = x;
}

deleteDeepest()関数による最深ノードの削除

deleteDeepest(struct Node* root, struct Node* deepestNode)関数は、渡されたノードが実際に最深ノードであるか、あるいはその左右の子が最深ノードであるかを確認します。該当する場合には、親ノード(deepestNode)をdeleteで解放する前に、対応する子ポインタをNULLに設定します。

void deleteDeepest(struct Node* root,
   struct Node* deepestNode){
      queue<struct Node*> q;
      q.push(root);
      struct Node* temp;
      while (!q.empty()) {
         temp = q.front();
         q.pop();
      if (temp == deepestNode) {
         temp = NULL;
         delete (deepestNode);
         return;
      }
      if (temp->rightChild) {
         if (temp->rightChild == deepestNode) {
            temp->rightChild = NULL;
            delete (deepestNode);
            return;
         }
         else
            q.push(temp->rightChild);
         }
      if (temp->leftChild) {
         if (temp->leftChild == deepestNode) {
            temp->leftChild = NULL;
            delete (deepestNode);
            return;
         }
         else
            q.push(temp->leftChild);
      }
   }
}

サンプルコード

以下の実装例では、二分木における実際の削除処理の一連の流れを確認できます。

#include <iostream>
#include <queue>
using namespace std;
struct Node {
   int data;
   struct Node *leftChild, *rightChild;
};
struct Node* NewNode(int data){
   struct Node* temp = new Node;
   temp->data = data;
   temp->leftChild = temp->rightChild = NULL;
   return temp;
};
void inorder(struct Node* temp){
   if (!temp)
      return;
   inorder(temp->leftChild);
   cout << temp->data << " ";
   inorder(temp->rightChild);
}
void deleteDeepest(struct Node* root,
   struct Node* deepestNode){
      queue<struct Node*> q;
      q.push(root);
      struct Node* temp;
      while (!q.empty()) {
         temp = q.front();
         q.pop();
         if (temp == deepestNode) {
            temp = NULL;
            delete (deepestNode);
            return;
         }
         if (temp->rightChild) {
            if (temp->rightChild == deepestNode) {
               temp->rightChild = NULL;
               delete (deepestNode);
               return;
            }
            else
               q.push(temp->rightChild);
         }
         if (temp->leftChild) {
            if (temp->leftChild == deepestNode) {
               temp->leftChild = NULL;
               delete (deepestNode);
               return;
            }
         else
            q.push(temp->leftChild);
         }
      }
   }
Node* deletion(struct Node* root, int data){
   if (root == NULL)
      return NULL;
   if (root->leftChild == NULL && root->rightChild == NULL) {
      if (root->data == data)
         return NULL;
      else
         return root;
   }
   queue<struct Node*> q;
   q.push(root);  
   struct Node* temp;
   struct Node* data_node = NULL;
while (!q.empty()) {
   temp = q.front();
   q.pop();
   if (temp->data == data)
      data_node = temp;
   if (temp->leftChild)
      q.push(temp->leftChild);
   if (temp->rightChild)
      q.push(temp->rightChild);
}
if (data_node != NULL) {
   int x = temp->data;
   deleteDeepest(root,temp);
   data_node->data = x;
   }
   return root;
}
// Driver code
int main(){
   struct Node* root = NewNode(12);
   root->leftChild = NewNode(13);
   root->leftChild->leftChild = NewNode(9);
   root->leftChild->rightChild = NewNode(14);
   root->rightChild = NewNode(11);
   root->rightChild->leftChild = NewNode(17);
   root->rightChild->rightChild = NewNode(10);
   cout << "Inorder traversal before deletion : ";
   inorder(root);
   int data = 13;
   root = deletion(root, data);
   cout << endl<< "Inorder traversal after deletion : ";
   inorder(root);
   return 0;
}

実行結果

上記のコードを実行すると、以下の出力が得られます。

Inorder traversal before deletion : 9 13 14 12 17 11 10
Inorder traversal after deletion : 9 10 14 12 17 11

この結果から、値13を持つノードが削除され、代わりに木の中で最も深く右側にあった値10がその位置へ移動していることが分かります。このように、最深ノードとの置換によって木の構造を崩さずにノードを削除できるのが、この手法の特徴です。

  1. C++で二分木の前順走査における先行ノード(Preorder Predecessor)を求める方法

    問題の概要 この問題では、二分木とあるノードの値が与えられ、そのノードの前順走査における先行ノード(Preorder Predecessor)を出力することが求められます。 用語の整理 二分木(Binary Tree)とは、各ノードが最大2つの子ノードを持つことができる特殊な木構造のことです。 前順走査(Preorder Traversal)は、木のノードを巡回する方法の一つで、「根ノード → 左の子 → 右の子」の順に訪問していきます。 前順先行ノードとは、前順走査において対象ノードの直前に訪問されるノードのことを指します。 具体例 次の例で問題を確認してみましょう。 入力: 1 出力:

  2. C++で二分木の前順走査における後続ノードを求める方法

    この問題では、二分木とあるノードの値が与えられ、そのノードの前順走査(プレオーダー)における後続ノードを出力することが求められます。基本用語の整理二分木(Binary Tree):各ノードが最大2つの子ノードを持つことができる特別な木構造です。前順走査(Preorder Traversal):木のノードを巡回する方法の1つで、「根ノード → 左の子 → 右の子」の順に訪問します。前順走査における後続ノード:前順走査の順序において、対象ノードの直後に現れるノードのことです。問題例具体例を見て、問題を理解しましょう。入力: 9 出力: 0 説明: この木の前順走査は「5 9 0 1 2 5」の順に