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

Pythonで全ノードに到達可能な最小の頂点集合を見つけるプログラム

問題概要

有向非巡回グラフ(DAG)を考えます。グラフにはn個の頂点があり、各ノードには0からn-1までの番号が付けられています。グラフはエッジリストとして表現され、edges[i] = (u, v)はノードuからノードvへ向かう有向エッジを意味します。このとき、そこから出発すればグラフ内のすべてのノードに到達できるような、最小の頂点集合を見つける必要があります(頂点は任意の順序で返して構いません)。

例えば、入力が次のような場合を考えてみましょう。

Pythonで全ノードに到達可能な最小の頂点集合を見つけるプログラム

この場合、出力は [0, 2, 3] となります。これらの頂点は他のどの頂点からも到達できないため、ここから探索を開始すれば全ノードをカバーできるからです。

解決のアプローチ

鍵となるのは「入ってくるエッジを持たない頂点」に着目することです。ある頂点が別の頂点から到達可能であるなら、必ずどこかからの有向エッジの終点になっているはずです。逆に言えば、どのエッジの終点にもなっていない頂点は、他の頂点から辿り着くことができません。したがって、求めるべき答えは「エッジリスト中に一度も終点として現れない頂点」の集合と一致します。

具体的には、以下の手順で解くことができます。

  • n := エッジリストのサイズ
  • all_nodes := 0からn-1までのすべての頂点を含む集合を作成
  • v := 空の集合を作成
  • edges内の各エッジ(i, j)について、終点であるjをvに追加
  • ans := all_nodesからvに含まれる要素を除外した差集合
  • ansを返す

実装例

それでは、実際のコードを見て理解を深めましょう。

def solve(edges):
    n = len(edges)
    all_nodes = set(range(n))
    v = set()
    for edge in edges:
        v.add(edge[1])
    ans = all_nodes - v
    return ans

edges = [(0,1),(2,1),(3,1),(1,4),(2,4)]
print(solve(edges))

入力

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

出力

{0, 2, 3}

計算量について

このアルゴリズムは、エッジリストを一度走査するだけなので、時間計算量はO(E)(Eはエッジ数)、空間計算量はO(V)(Vは頂点数)となり、非常に効率的です。集合演算を活用することで、簡潔かつ高速に最小の起点となる頂点集合を求められます。

  1. Pythonで同じラベルを持つサブツリー内のノード数を求めるプログラム

    ここでは、n個のノードからなる根付きの一般木を考えます。ノードには0からn-1までの番号が振られており、各ノードには小文字の英字ラベルが割り当てられています。ラベルは配列labelsとして与えられ(labels[i]がi番目のノードのラベル)、木は辺リストで表現されます。各辺eは[u, v]という形式で、uが親、vが子であることを意味します。 求めたいのは、サイズnの配列Aです。A[i]には「i番目のノードと同じラベルを持つ、そのサブツリー内のノードの総数」を格納します。 例えば、入力が次のような場合を考えてみましょう。 n = 5、label = ccaca のとき、出力は [3, 2,

  2. Pythonで二分探索木(BST)の指定範囲内にあるノード数を求める方法

    問題の概要 二分探索木(BST)が与えられ、さらに左側の境界値 l と右側の境界値 r が指定されます。このとき、木に含まれるすべてのノードの中で、値が l 以上 r 以下の範囲内にあるノードの個数を求めるのが目的です。 例えば、次のような木が与えられたとします。 このとき l = 7、r = 13 とすると、範囲内に含まれるノードは 8、10、12 の3つなので、出力は 3 になります。 アルゴリズムの考え方 スタックを使った反復的な深さ優先探索(DFS)で木をたどります。重要なポイントは、二分探索木の性質を活かして枝刈り(pruning)を行うことです。値が境界より小さいノードの左側の