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

C++で二分木の葉ノードを繰り返し収集・削除するアルゴリズム


問題の概要

二分木が与えられているとします。まずすべての葉(子ノードを持たないノード)を収集して取り除き、その操作を木が空になるまで繰り返します。

例えば、次のような二分木が入力として与えられた場合を考えてみます。

C++で二分木の葉ノードを繰り返し収集・削除するアルゴリズム

このとき、出力は [[4,5,3],[2],[1]] となります。最初のラウンドで葉である 4、5、3 が取り除かれ、続いて 2 が、最後に根の 1 が残るという流れです。

解法のアプローチ

この問題は、各ノードの「高さ」(最も深い葉から数えた距離)をDFSで求めると効率的に解けます。同じ高さを持つノードは、必ず同じラウンドで葉になるためです。具体的な手順は以下の通りです。

  • マップ sz を定義する

  • 2次元配列 ret を定義する

  • 引数にノードを取る関数 dfs() を定義する

  • node が null の場合は 0 を返す

  • sz[node の値] に「1 + max(dfs(node の左の子), dfs(node の右の子))」を代入する

  • ret のサイズが sz[node の値] より小さい場合、空の配列 temp を作成して ret の末尾に追加する

  • ret[sz[node の値] - 1] の末尾に node の値を挿入する

  • sz[node の値] を返す

メインの処理では、dfs(root) を呼び出した後、結果として ret を返すだけで完成です。

実装例

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

#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<vector<auto>> v){
   cout << "[";
   for(int i = 0; i<v.size(); i++){
      cout << "[";
      for(int j = 0; j <v[i].size(); j++){
         cout << v[i][j] << ", ";
      }
      cout << "],";
   }
   cout << "]"<<endl;
}
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 Solution {
public:
   unordered_map <int, int> sz;
   vector < vector <int> > ret;
   int dfs(TreeNode* node){
      if(!node) return 0;
      sz[node->val] = 1 + max(dfs(node->left), dfs(node->right));
      if(ret.size() < sz[node->val]){
         vector <int> temp;
         ret.push_back(temp);
      }
      ret[sz[node->val] - 1].push_back(node->val);
      return sz[node->val];
   }
   vector<vector<int>> findLeaves(TreeNode* root) {
      dfs(root);
      return ret;
   }
};
main(){
   Solution ob;
   vector<int> v = {1,2,3,4,5};
   TreeNode *root = make_tree(v);
   print_vector(ob.findLeaves(root));
}

入力

{1,2,3,4,5}

出力

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

出力の内側の配列1つひとつが、葉を取り除く各ラウンドに対応しています。なお、DFSの走査順序によって、同じグループ内の値の並び順は前後することがあります(例:[[4,5,3],...] と [[3,5,4],...])。どちらの順序であっても、グルーピングとしては正しい結果です。

計算量について

この手法では各ノードを一度だけ訪問すればよいため、時間計算量は O(N)(N はノード数)、必要な記憶領域も O(N) に抑えられます。毎回木全体を走査しながら葉を削除していく素朴な方法では O(N²) かかる可能性がありますが、高さを事前に計算しておくこのアプローチなら線形時間で処理できる点が大きな利点です。


  1. C++で二分木の各レベルにおける最大の積を求めるアルゴリズム

    問題の概要 正の値と負の値が混在するノードで構成された二分木が与えられたとします。このとき、木の各レベルに存在するノードの値の積を計算し、その中で最大となる値を求める必要があります。 例として、次のような二分木を考えてみましょう。 この木の場合、各レベルの積は以下のように計算できます。 レベル0の積:4 レベル1の積:2 × (-5) = -10 レベル2の積:(-1) × 3 × (-2) × 6 = 36 したがって、この木における最大のレベル積は 36 となります。 解決のアプローチ この問題は、木をレベル順走査(幅優先探索・BFS)でたどることで効率的に解けます。キューを利用して

  2. C++で二分木の最大垂直和を求める方法

    はじめに二分木が与えられたとき、垂直順序走査における各垂直列のノード値の合計を計算し、その中から最大値を求めて出力するのが本記事の課題です。例として、以下のような二分木を考えてみましょう。この二分木を垂直順序走査すると、各列の合計は次のようになります。4 2 1 + 5 + 6 = 12 3 + 8 = 11 7 9各列の合計の中で最大となるのは 12 です。アルゴリズムの考え方アプローチはシンプルです。幅優先探索(BFS)を用いて垂直順序走査を行い、各ノードに水平距離を割り当てます。ルートの水平距離を 0 とし、左に移動するごとに -1、右に移動するごとに +1 とします。同じ水平距離を持つ