C++で2つの二分木をマージする方法
2つの二分木があるとします。一方の木をもう一方の木に重ねてみると、一部のノードは互いに重なり合い、残りのノードは重ならない状態になります。ここで、この2つの木を1つの新しい二分木へマージすることを考えます。
マージのルールは次のとおりです。2つのノードが重なっている場合は、それらの値を合計したものをマージ後のノードの新しい値とします。どちらか一方しかノードが存在しない場合は、空でない方のノードをそのまま新しい木のノードとして使用します。
たとえば、次のような2つの木が与えられたとします。

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

解法のアプローチ
この問題を解くために、以下の手順に従います。
メソッド名は mergeTrees() とし、2つのツリーノード n1 と n2 を引数として受け取ります。
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つの二分木のマージを簡潔に実装できます。各ノードに対して「両方存在すれば値を足す」「片方だけならそのノードを採用する」というルールを適用しながら、左右の子に対して同じ処理を繰り返すだけでよいため、非常に直感的なアルゴリズムとなっています。
-
C++で解く「ユニークな二分探索木 II」― 再帰で全パターンのBSTを生成する方法
整数 n が与えられたとき、1 から n までの値を格納する、構造的にユニークな二分探索木(BST)をすべて生成することを考えます。例えば、入力が 3 の場合、生成される木は以下のようになります。解法のアプローチこの問題は再帰(バックトラッキング)を用いることで効率的に解けます。二分探索木の性質上、ある値 i を根にしたとき、左部分木には i より小さい値が、右部分木には i より大きい値が属します。この性質を利用して、各値を根とした場合の左右の部分木を再帰的に生成していきます。アルゴリズムの手順low と high を引数に取る再帰関数 generate() を定義します。結果を格納するため
-
C++で2つの二分木の最初の一致しない葉を見つける方法
2つの二分木が与えられたとき、両方の木を前順(先行順)で走査した際に最初に一致しない葉ノードを見つける問題を考えます。すべての葉が一致している場合は、何も出力しません。問題の例次のような2つの二分木があるとします。この場合、前順走査の順序で葉を比較していくと、最初に一致しない葉は 11 と 15 になります。アルゴリズムの考え方この問題は、スタックを用いた反復的な前順走査(preorder traversal)を2つの木に対して同時に実行することで解けます。ポイントは以下の通りです。木ごとに独立したスタックを用意するスタックの先頭が葉ノードになるまで、子ノードをプッシュし続ける両スタックの先頭