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

C++で汚染された二分木を復元して要素を検索する方法

問題の概要

次のようなルールに従う二分木を考えます。

  • root.val == 0 である
  • treeNode.val が x であり、treeNode.left が NULL でない場合、treeNode.left.val = 2 * x + 1 となる
  • treeNode.val が x であり、treeNode.right が NULL でない場合、treeNode.right.val = 2 * x + 2 となる

ここで、この二分木は「汚染」されているものとします。つまり、すべてのノードの値が -1 に書き換えられている状態です。まず二分木を復元した上で、以下の FindElements クラスを実装する必要があります。

  • FindElements(TreeNode* root):汚染された二分木を受け取ってオブジェクトを初期化します。最初に木を復元しなければなりません。
  • bool find(int target):復元後の二分木に target の値が存在するかどうかを返します。

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

C++で汚染された二分木を復元して要素を検索する方法

復元後に 1、3、5 を検索すると、結果はそれぞれ true、true、false となります。

解法のアプローチ

この問題は、DFS(深さ優先探索)を使って木を一度だけ走査し、復元した値をすべて集合に記録しておくことで効率的に解けます。手順は以下の通りです。

  • 整数の集合(set)a を定義します。
  • dfs() メソッドを定義します。引数は root と rootVal で、rootVal の初期値は -1 です。
  • root が NULL の場合はそのまま戻ります。
  • rootVal が -1 の場合はノードの値を 0 に設定し、それ以外の場合は rootVal をそのまま設定します。
  • ノードの値を集合 a に挿入します。
  • dfs(左の子, 2 * ノードの値 + 1) と dfs(右の子, 2 * ノードの値 + 2) を再帰的に呼び出します。
  • 初期化(復元)時には dfs(root, -1) を呼び出します。
  • find() では、目的の値が集合 a に存在するかを確認し、存在すれば true、存在しなければ false を返します。

この方法では、復元に O(n) の計算量がかかる一方、find() は std::set(平衡二分探索木)による検索のため O(log n) で高速に判定できます。

以下の実装を見ると、より理解が深まるでしょう。

コード例

#include <bits/stdc++.h>
using namespace std;
class TreeNode{
   public:
   int val;
   TreeNode *left, *right;
   TreeNode(int data){
      val = data;
      left = NULL;
      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){
         if(val != NULL)
          temp->left = new TreeNode(val);
         else
          temp->left = new TreeNode(0);
         return;
      }else{
         q.push(temp->left);
      }
      if(!temp->right){
         if(val != NULL)
          temp->right = new TreeNode(val);
         else
          temp->right = new TreeNode(0);
         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;
}
class FindElements {
   public:
   set <int> a;
   void dfs(TreeNode* root,int rootVal=-1){
      if(!root)return;
      root->val = rootVal == -1?0:rootVal;
      a.insert(root->val);
      dfs(root->left,2*root->val + 1);
      dfs(root->right, 2*root->val + 2);
   }
   FindElements(TreeNode* root) {
      dfs(root);
   }
   bool find(int t) {
      return a.find(t)!=a.end();
   }
};
main(){
   vector<int> v = {-1,-1,-1,-1,-1};
   TreeNode *root = make_tree(v);
   FindElements ob(root);
   cout << (ob.find(1)) << endl;
   cout << (ob.find(3)) << endl;
   cout << (ob.find(5)) << endl;
}

入力

木を [-1,-1,-1,-1,-1] で初期化し、find(1)、find(3)、find(5) を呼び出す

出力

1
1
0

出力は 1(true)、1(true)、0(false)となり、復元後の木には 1 と 3 は存在するが 5 は存在しないことが確認できます。

  1. C++で二分木をリンクリストにフラット化(平坦化)する方法

    二分木が与えられたとき、それをその場(in-place)でリンクリストへフラット化(平坦化)することを考えます。具体的には、すべてのノードを右ポインタで連結し、左ポインタを null にした、一本の連結リストのような構造へ変換します。例えば、次のような二分木があるとします。これをフラット化すると、出力は次のようになります。アルゴリズムの手順この問題は、逆後順走査(右 → 左 → 根)を利用することで効率的に解けます。手順は以下の通りです。prev を null で初期化します。ルートを引数にとる再帰関数 solve() を定義します。root が null の場合は、そのまま戻ります。まず r

  2. C++で二分木のルートから特定ノードまでの距離を求める方法

    二分木が与えられたとき、ルートから特定のノード u までの距離(経路の長さ)を求める問題を考えてみましょう。例として、次のような二分木を想定します。この木において、ルートからノード6までの距離は2、ルートからノード8までの距離は3となります。解決のアプローチこの問題は、再帰的な手法を用いて解くことができます。具体的には、目的のノードを左部分木と右部分木の両方に対して再帰的に探索し、再帰の各段階(レベル)で距離を1ずつ加算していきます。探索の仕組みは以下の通りです。現在のノードがNULLの場合は -1 を返します(ノードが見つからなかったことを示す)。現在のノードの値が目的の値と一致した場合、ま