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

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