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

C++で2つの二分木をマージする方法

2つの二分木があるとします。一方の木をもう一方の木に重ねてみると、一部のノードは互いに重なり合い、残りのノードは重ならない状態になります。ここで、この2つの木を1つの新しい二分木へマージすることを考えます。

マージのルールは次のとおりです。2つのノードが重なっている場合は、それらの値を合計したものをマージ後のノードの新しい値とします。どちらか一方しかノードが存在しない場合は、空でない方のノードをそのまま新しい木のノードとして使用します。

たとえば、次のような2つの木が与えられたとします。

C++で2つの二分木をマージする方法

このときの出力結果は以下のようになります。

C++で2つの二分木をマージする方法

解法のアプローチ

この問題を解くために、以下の手順に従います。

  • メソッド名は mergeTrees() とし、2つのツリーノード n1n2 を引数として受け取ります。

  • n1が空(NULL)でn2が空でない場合 → n2を返します。逆にn2が空でn1が空でない場合 → n1を返します。両方がNULLの場合 → NULLを返します。

  • n1の値 ← n1の値 + n2の値(重なったノードの値を合計)

  • n1の左部分木 ← mergeTrees(n1の左部分木, n2の左部分木)

  • n1の右部分木 ← mergeTrees(n1の右部分木, n2の右部分木)

  • 最後にn1を返します。

このアルゴリズムは再帰的に動作し、時間計算量・空間計算量ともにO(min(m, n))となります(mとnはそれぞれの木のノード数)。これは、マージ処理が両方の木で共通して存在するノードのみを訪問するためです。

例(C++実装)

理解を深めるために、以下の実装を見てみましょう。

#include <bits/stdc++.h>
using namespace std;
class TreeNode{
    public:
        int val;
        TreeNode *left, *right;
        TreeNode(int data){
            val = data;
            left = right = NULL;
        }
};
void insert(TreeNode **root, int val){
    queue<TreeNode*> q;
    q.push(*root);
    while(q.size()){
        TreeNode *temp = q.front();
        q.pop();
        if(!temp->left){
            temp->left = new TreeNode(val);
            return;
        }
        else{
            q.push(temp->left);
        }
        if(!temp->right){
            temp->right = new TreeNode(val);
            return;
        }
        else{
            q.push(temp->right);
        }
    }
}
TreeNode *make_tree(vector<int> v){
    TreeNode *root = new TreeNode(v[0]);
    for(int i = 1; i<v.size(); i++){
        insert(&root, v[i]);
    }
    return root;
}
void tree_level_trav(TreeNode*root){
    if (root == NULL) return;
    cout << "[";
    queue<TreeNode *> q;
    TreeNode *curr;
    q.push(root);
    q.push(NULL);
    while (q.size() > 1) {
        curr = q.front();
        q.pop();
        if (curr == NULL){
            q.push(NULL);
        }
        else {
            if(curr->left)
                q.push(curr->left);
            if(curr->right)
                q.push(curr->right);
            if(curr->val == 0){
                cout << "null" << ", ";
            }
            else{
                cout << curr->val << ", ";
            }
        }
    }
    cout << "]"<<endl;
}
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* n1, TreeNode* n2) {
        if(!n1 && n2){
            return n2;
        }
        else if(!n2 && n1)return n1;
        else if(!n1 && !n2)return NULL;
        n1->val+=n2->val;
        n1->left = mergeTrees(n1->left,n2->left);
        n1->right = mergeTrees(n1->right,n2->right);
        return n1;
    }
};
main(){
    Solution ob;
    vector<int> v1 = {1,3,2,5};
    vector<int> v2 = {2,1,3,NULL,4,NULL,7};
    TreeNode *root1 = make_tree(v1);
    TreeNode *root2 = make_tree(v2);
    root1 = ob.mergeTrees(root1, root2);
    tree_level_trav(root1);
}

入力

[1,3,2,5]
[2,1,3,null,4,null,7]

出力

[3, 4, 5, 5, 4, null, 7]

まとめ

このように、再帰を使うことで2つの二分木のマージを簡潔に実装できます。各ノードに対して「両方存在すれば値を足す」「片方だけならそのノードを採用する」というルールを適用しながら、左右の子に対して同じ処理を繰り返すだけでよいため、非常に直感的なアルゴリズムとなっています。

  1. C++で解く「ユニークな二分探索木 II」― 再帰で全パターンのBSTを生成する方法

    整数 n が与えられたとき、1 から n までの値を格納する、構造的にユニークな二分探索木(BST)をすべて生成することを考えます。例えば、入力が 3 の場合、生成される木は以下のようになります。解法のアプローチこの問題は再帰(バックトラッキング)を用いることで効率的に解けます。二分探索木の性質上、ある値 i を根にしたとき、左部分木には i より小さい値が、右部分木には i より大きい値が属します。この性質を利用して、各値を根とした場合の左右の部分木を再帰的に生成していきます。アルゴリズムの手順low と high を引数に取る再帰関数 generate() を定義します。結果を格納するため

  2. C++で2つの二分木の最初の一致しない葉を見つける方法

    2つの二分木が与えられたとき、両方の木を前順(先行順)で走査した際に最初に一致しない葉ノードを見つける問題を考えます。すべての葉が一致している場合は、何も出力しません。問題の例次のような2つの二分木があるとします。この場合、前順走査の順序で葉を比較していくと、最初に一致しない葉は 11 と 15 になります。アルゴリズムの考え方この問題は、スタックを用いた反復的な前順走査(preorder traversal)を2つの木に対して同時に実行することで解けます。ポイントは以下の通りです。木ごとに独立したスタックを用意するスタックの先頭が葉ノードになるまで、子ノードをプッシュし続ける両スタックの先頭