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

【C++】二分木のルートからリーフへの全経路を相対位置付きで出力する方法

問題の概要

この記事では、二分木が与えられたときに、ルート(根)からリーフ(葉)までのすべての経路を出力する方法を解説します。出力の際には、アンダースコア「_」を用いて各ノードの相対的な水平位置を視覚的に表現します。

まず、具体例を見ながら内容を理解していきましょう。

入力:

【C++】二分木のルートからリーフへの全経路を相対位置付きで出力する方法

出力:

_ _ 3
_ 9
1
_3
9
_7
3
_ 4
_ _ 2
3
9 4
1 7 6 2
3
_ 4
6

解決のアプローチ:垂直順序の活用

この問題を解く鍵となるのは、木の要素の垂直順序(vertical order)という概念です。

【C++】二分木のルートからリーフへの全経路を相対位置付きで出力する方法

上図のように、ルートの水平距離を0とし、左の子へ移動するたびに-1、右の子へ移動するたびに+1として水平距離を計算します。この水平距離の差分に応じてアンダースコアの数を決めることで、ノードの相対位置を表現した経路を出力できます。

アルゴリズム

ステップ1: 二分木を先行順巡回(preorder traversal)で走査します。走査中に、移動方向に基づいて各ノードの水平距離を計算します。ルートの水平距離は0とし、上記の図のように処理します。
ステップ2: リーフノードに到達した時点で、そこまでの経路をアンダースコア「_」付きで出力します。

C++による実装例

#include<bits/stdc++.h>
using namespace std;
#define MAX_PATH_SIZE 1000
struct Node{
    char data;
    Node *left, *right;
};
Node * newNode(char data){
    struct Node *temp = new Node;
    temp->data = data;
    temp->left = temp->right = NULL;
    return temp;
}
struct PATH{
    int horizontalDistance;
    char key;
};
void printPath(vector < PATH > path, int size){
    int minimumhorizontalDistance = INT_MAX;
    PATH p;
    for (int it=0; it<size; it++){
        p = path[it];
        minimumhorizontalDistance = min(minimumhorizontalDistance, p.horizontalDistance);
    }
    for (int it=0; it < size; it++){
        p = path[it];
        int noOfUnderScores = abs(p.horizontalDistance -minimumhorizontalDistance);
        for (int i = 0; i < noOfUnderScores; i++) cout<<"_ ";
            cout<<p.key<<endl;
    }
    cout<<"\nNext Path\n";
}
void printAllRtLPaths(Node *root, vector < PATH > &AllPath, int horizontalDistance, int order ){
    if(root == NULL)
        return;
    if (root->left == NULL && root->right == NULL){
        AllPath[order] = (PATH { horizontalDistance, root->data });
        printPath(AllPath, order+1);
        return;
    }
    AllPath[order] = (PATH { horizontalDistance, root->data });
    printAllRtLPaths(root->left, AllPath, horizontalDistance-1, order+1);
    printAllRtLPaths(root->right, AllPath, horizontalDistance+1, order+1);
}
void printRootToLeafPath(Node *root){
    if (root == NULL)
        return;
    vector<PATH> Allpaths(MAX_PATH_SIZE);
    printAllRtLPaths(root, Allpaths, 0, 0);
}
int main(){
    Node *root = newNode('3');
    root->left = newNode('9');
    root->right = newNode('4');
    root->left->left = newNode('1');
    root->left->right = newNode('7');
    root->right->left = newNode('6');
    root->right->right = newNode('2');
    printRootToLeafPath(root);
    return 0;
}

実行結果

_ _ 3
_ 9
1
Next Path
_ 3
9
_ 7
Next Path
3
_ 4
6
Next Path
3
_ 4
_ _ 2

出力の読み方

このプログラムは、リーフノードに到達するたびに経路を出力し、「Next Path」で区切ります。各行の先頭にあるアンダースコアの数は、そのノードが経路内で最も左にあるノードからどれだけ右に離れているかを表しています。例えば最初の経路「3 → 9 → 1」では、ノード1が最も左(水平距離-2)に位置するため、ノード3には2つ、ノード9には1つのアンダースコアが付きます。

計算量について

木の走査は各ノードを一度ずつ訪問するため、時間計算量はO(n)です。ただし、リーフに到達するたびに経路全体を出力するため、出力にかかるコストは木の高さhに対してO(h)となり、全体としてはO(n・h)となります。

まとめ

本記事では、二分木のルートからリーフへのすべての経路を、アンダースコアで相対位置を示しながら出力するC++プログラムを紹介しました。先行順巡回と水平距離の計算を組み合わせることで、ノードの空間的な配置を反映した直感的な出力を実現できます。二分木の走査アルゴリズムの理解を深める良い練習問題なので、ぜひ自分でも実装してみてください。

  1. C++で再帰を使わずに二分木のルートからリーフへの経路を出力するプログラム

    このチュートリアルでは、与えられた二分木において、ルートノードからすべてのリーフノード(葉ノード)への経路を出力するプログラムを、C++で再帰を使わずに実装する方法を解説します。例として、次のような二分木を考えてみましょう。この二分木には、34・55・29という3つのリーフノードが存在します。したがって、ルートノードからリーフノードへの経路は3つあることになります。アルゴリズムのアプローチこの問題は、再帰に頼らない反復的なアプローチで解くことができます。手順は以下のとおりです。スタックを用いて、二分木を前順走査(先行順走査)します。走査の過程で、各ノードの親ノードへのポインタをマップ(std:

  2. C++で再帰を使わずに二分木のルートからリーフまでのパスを出力する方法

    二分木が与えられたとき、ルートからリーフ(葉)までの複数のパスをすべて出力する必要があります。しかし、ここでの課題は再帰を使用せずに実装することです。通常、木の探索には再帰がよく使われますが、今回は制約として再帰が使えないため、反復処理(イテレーティブな方法)で木を走査します。そのために、STLのmapを活用します。このマップには各ノードとその親ノードの対応関係を格納し、レベル順走査(またはスタックを用いた走査)によってリーフノードを検出した時点で、親へのポインタをたどることでルートからリーフまでのパスを出力できます。上記の二分木の場合、ルートからリーフまで到達するためのパスは以下のように複数