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

【Python】グラフ内の2つのノードに共通して到達可能なノードが存在するかを判定するプログラム

問題概要

有向グラフのエッジリストが与えられます。グラフは n 個のノードから構成され、ノード名は 0 から n-1 までです。さらに、2つの整数値 a と b が与えられます。ここで、「あるノード c から a への経路と、c から b への経路がどちらも存在する」という条件を満たすノード c が存在するかどうかを判定するのが課題です。

例として、下図のようなグラフを考えてみましょう。

【Python】グラフ内の2つのノードに共通して到達可能なノードが存在するかを判定するプログラム

a = 2、b = 3 の場合、出力は True になります。これは c = 0 とおくと、0 から 2 への経路と 0 から 3 への経路がどちらも存在するためです。

解法の考え方:逆グラフとDFSの組み合わせ

この問題は深さ優先探索(DFS)を使えば効率的に解くことができます。鍵となるのは、エッジの向きをすべて反転させた「逆グラフ」を事前に作成しておく点です。

逆グラフ上であるノード a から DFS を実行すると、「a に到達可能なすべてのノード」を列挙できます。同じ操作を b に対しても行い、得られた2つの訪問済みノード集合の共通部分を調べればよいのです。共通部分が空でなければ、その要素こそが「a にも b にも到達できるノード c」に該当します。

アルゴリズムの手順

  • DFS関数を定義する(引数: graph, node, visited)
    • node が未訪問の場合:
      • node を訪問済みとしてマークする
      • graph[node] 内の各ノード x に対して、DFS(graph, x, visited) を再帰的に呼び出す
  • メイン処理(solve関数)では以下を実行する:
    • エッジリストから隣接リスト形式の逆グラフを生成する
    • 空の集合 visited_a と visited_b を用意する
    • DFS(graph, a, visited_a) を実行する
    • DFS(graph, b, visited_b) を実行する
    • visited_a と visited_b の共通部分を新しいリストとして取得する
    • 共通部分が空でなければ True を返し、空なら False を返す

Pythonでの実装例

それでは、実際のコードを見てみましょう。

def edge_list_to_graph(edges):
    s = set()
    for x, y in edges:
        s.add(x)
        s.add(y)
    s = len(list(s))
    graph = [[] for x in range(s)]
    for x, y in edges:
        graph[y].append(x)
    return graph

def DFS(graph, node, visited):
    if node not in visited:
        visited.add(node)
        for x in graph[node]:
            DFS(graph, x, visited)

def solve(edges, a, b):
    graph = edge_list_to_graph(edges)

    visited_a, visited_b = set(), set()

    DFS(graph, a, visited_a)
    DFS(graph, b, visited_b)

    ans = list(visited_a.intersection(visited_b))
    if ans:
        return True
    return False

ed_list = [(0, 4),(4, 3),(1, 2),(0, 1),(0, 2),(1, 1)]
a = 2
b = 3
print(solve(ed_list, a, b))

入力

[(0, 4),(4, 3),(1, 2),(0, 1),(0, 2),(1, 1)], 2, 3

出力

True

コードのポイント解説

  • edge_list_to_graph関数: エッジリストに登場する全ノードを一旦集合に集めてノード総数を求め、そのサイズ分の隣接リストを初期化します。注目すべきは graph[y].append(x) の部分で、エッジ (x, y) を逆向き(y → x)として登録しています。これにより逆グラフが構築されます。
  • DFS関数: 再帰的な深さ優先探索の標準的な実装です。訪問済みノードは set で管理しているため、追加や存在確認が O(1) で行えるのが利点です。
  • solve関数: a と b それぞれからの DFS 結果を set の intersection() メソッドで照合し、共通ノードが存在するかどうかを判定します。

計算量

ノード数を V、エッジ数を E とすると、各 DFS の計算量は O(V + E) であり、共通部分の算出は O(V) で済みます。したがって、全体の計算量は O(V + E) となり、非常に効率的なアプローチだと言えます。

  1. Pythonで与えられたグラフが2部グラフかどうかを判定するプログラム

    2部グラフとは無向グラフが与えられたとき、そのグラフが2部グラフ(バイパータイトグラフ)であるかどうかを判定する方法を解説します。2部グラフとは、グラフのすべての頂点を2つの集合 A と B に分割でき、グラフ内のすべての辺 {u, v} が必ず一方の端点 u が集合 A、もう一方の端点 v が集合 B に属するようなグラフのことです。つまり、同じ集合内の頂点同士を結ぶ辺(A-A や B-B)が一切存在しないグラフです。例として、次のようなグラフを考えてみましょう。この場合、頂点 [0, 4] を集合 A に、[1, 2, 3] を集合 B に分類できます。すべての辺は A から B、または

  2. 指定された文字列がキーワードであるかどうかを確認するPythonプログラム

    この記事では、指定された文字列がPythonのキーワード(予約語)であるかどうかを判定する方法について解説します。問題の概要与えられた文字列が、Pythonにおけるキーワードであるかどうかを確認する必要があります。キーワードとは、言語によって特別な用途のために予約されている単語であり、変数名や関数名などの識別子として使用することはできません。例えば「if」「for」「while」「def」などはすべてキーワードです。これらの名前を変数に使おうとすると、構文エラーが発生します。解決策:keywordモジュールの活用Pythonには標準ライブラリとしてkeywordモジュールが用意されており、これ