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

【C++】2つの特定のノード間にパスが存在するかどうかを確認するプログラム

グラフにおいて「ある頂点から別の頂点へ到達できるか」を判定する問題は、経路探索やネットワーク解析など、さまざまな場面で登場する基本的な課題です。本記事では、C++を用いて、指定した2つのノード(頂点)間にパス(経路)が存在するかどうかを確認するプログラムを紹介します。

アルゴリズム

このプログラムでは、幅優先探索(BFS)をベースとした関数 isReach() を使い、始点 s から終点 d への到達可能性を調べます。処理の手順は以下の通りです。

開始
  isReach() は、d が s から到達可能かどうかを判定する再帰的な関数である:
  A) すべての頂点を「未訪問」としてマークする。
  B) 現在のノードを「訪問済み」としてマークし、キューに追加する。
    キューは、ある頂点の隣接頂点をすべて取得するために使用される。
  C) キューから頂点を1つ取り出す(デキュー)。
  D) 取り出した頂点 s に隣接するすべての頂点を取得する。
  E) 隣接頂点が未訪問であれば、「訪問済み」としてマークし、キューに追加する。
  F) その隣接ノードが目的地のノードであれば true を返す。
    そうでなければ BFS を継続する。
終了

C++による実装例

以下のコードでは、隣接リスト(adjacency list)形式でグラフを表現し、BFSによって2頂点間の到達可能性を判定しています。

#include <iostream>
#include <list>
using namespace std;
class G {
    int n;
    list<int> *adj;
    public:
        G(int n);
        void addEd(int x, int w);
        bool isReach(int s, int d);
};
G::G(int n) { //コンストラクタ
    this->n = n;
    adj = new list<int> [n];
}
void G::addEd(int x, int w) { //グラフに辺を追加する
    adj[x].push_back(w); //x のリストに w を追加する
}
bool G::isReach(int s, int d) {
    if (s == d)
    return true;
    bool *visited = new bool[n];
    //すべての頂点を未訪問としてマークする
    for (int i = 0; i < n; i++)
        visited[i] = false;
    list<int> queue;
    //現在のノードを訪問済みとしてマークし、キューに追加する
    visited[s] = true;
    queue.push_back(s);
    list<int>::iterator i;
    while (!queue.empty()) {
        s = queue.front();
        queue.pop_front(); //キューから頂点を取り出す
        //隣接頂点が未訪問の場合
        for (i = adj[s].begin(); i != adj[s].end(); ++i) {
            if (*i == d)
                return true;
            if (!visited[*i]) {
                visited[*i] = true;
                queue.push_back(*i);
            }
        }
    }
    return false;
}
int main() {
    G g(4);
    g.addEd(1, 3);
    g.addEd(0, 1);
    g.addEd(2, 3);
    g.addEd(1, 0);
    g.addEd(2, 1);
    g.addEd(3, 1);
    cout << "始点と終点の頂点を入力してください:(0〜3)";
    int a, b;
    cin >> a >> b;
    if (g.isReach(a, b))
        cout << "\n" << a << " から " << b << " へのパスが存在します";
    else
        cout << "\n" << a << " から " << b << " へのパスは存在しません";
    int t;
    t = a;
    a = b;
    b = t;
    if (g.isReach(a, b))
        cout << "\n" << a << " から " << b << " へのパスが存在します";
    else
        cout << "\n" << a << " から " << b << " へのパスは存在しません";
    return 0;
}

コードのポイント

  • クラス G:頂点数 n と、各頂点の隣接頂点を格納するリスト配列 adj をメンバとして持ちます。
  • addEd():頂点 x から頂点 w への有向辺をグラフに追加します。
  • isReach():始点 s と終点 d が一致していれば即座に true を返します。それ以外は BFS で探索を進め、d に到達できれば true、キューが空になっても到達できなければ false を返します。
  • main():4頂点のグラフを構築し、入力された始点・終点について双方向(a→b と b→a)の到達可能性をそれぞれチェックします。addEd() が有向辺を追加するため、片方向だけが到達可能なケースも正しく判定できます。

実行結果

始点と終点の頂点を入力してください:(0〜3)
3 から 1 へのパスが存在します
1 から 3 へのパスが存在します

計算量について

BFSでは各頂点と各辺を高々1回ずつ処理するため、時間計算量は O(V + E) です。また、訪問状態を記録する配列とキューが必要となるため、空間計算量は O(V) となります(V は頂点数、E は辺数)。大規模なグラフでも効率的に動作するため、到達可能性の判定には広く使われる手法です。

  1. C++で二分木内の任意の2つのノード間のパスのXORを求める

    この問題では、二分木とその木に含まれる2つのノードが与えられます。求めるのは、この2つのノードを結ぶパス上に存在するすべてのノードの値のXOR(排他的論理和)です。 具体例を使って問題を確認しましょう。 1 / \ 6 3 / \ / \ 2 4 7 5 上の二分木において、ノード2からノード3までのパス上の全ノードのXORを求めます。 ノード2からノード3へのパスは「2 → 6 → 1 → 3」です。 この解法では、ルートから各ノードまでの累積XORを利用します。path[2] = 1 ⊕ 6 ⊕ 2 = 5、p

  2. 【C++】二分木内の任意の2つのノード間のパスを出力する方法

    はじめに 本記事では、C++プログラミングにおいて二分木(バイナリツリー)内の任意の2つのノード間のパス(経路)を出力する方法を解説します。 前提として、すべてのノードが互いに異なる値を持つ二分木が与えられ、その中から指定した2つのノードをつなぐ経路を出力することを目標とします。 例として、次のような二分木を考えます。 具体例: ノード140からノード211までの経路を出力したい場合、期待される出力は以下の通りです。 Output: 140->3->10->211 解決のアプローチ 基本的なアイデアは、「ルートノードから目的の2つのノードそれぞれへの経路」を求め、それらを