0Pricing
DSA Interview Prep · レッスン

Kosarajuによる強連結成分

元のグラフでDFSを実行して終了順を取得し、グラフを転置してから終了順の逆順にもう一度DFSを実行し、SCCを特定します。

「Kosarajuによる強連結成分」はCoddyKit上の無料DSA Interview Prepレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはDSA Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 DSA Interview Prepコースには全4レッスンが含まれています。

強連結成分の定義

有向グラフの強連結成分(SCC)とは、その集合内の任意のノードから他の任意のノードへ到達する経路が存在する、極大なノード集合です。たとえば、ノードA、B、Cがサイクル(A→B→C→A)を形成している場合、それらはすべて同じSCCに属します。自己ループを持たない単一のノードも、それ自体で1つのSCCです。SCCによって、有向グラフの循環構造を明らかにできます。

Kosaraju法:2回のDFSパス

Kosaraju法は、2回のDFSパスを使ってO(V + E)でSCCをすべて見つけます。パス1では、元のグラフに対してDFSを実行し、完了順(ポストオーダー)にノードをスタックへプッシュします。パス2では、転置(反転)グラフに対して、完了順を逆にした順序(スタックからポップした順序)でDFSを実行します。パス2における各DFS木が1つのSCCになります。

Kosaraju法が機能する理由

パス1では、DFS木の完了が最後になるSCCは、他のSCCへの出ていくエッジを持たないSCCです(縮約DAGにおける「シンク」SCC)。転置グラフでは、このSCCは他のSCCから入ってくるエッジを持たないため、そこからパス2のDFSを開始すると、そのSCC内にとどまります。以降のパス2のDFSも、それぞれのSCC内にとどまります。これは、SCC間のすべてのエッジが反転され、すでに訪問済みのSCCへ向かうためです。

パス1:完了順の構築

元のグラフに対してDFSを実行し、各ノードの処理が終わった後(ポストオーダー)にそのノードをスタックへプッシュします。このパスでは成分自体には関心を持たず、完了順だけを求めます。最後に処理を終えるノードは、縮約DAGにおける「ソース」SCCに属します。

from collections import defaultdict

def kosaraju(n, edges):
    graph = defaultdict(list)
    rev_graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
        rev_graph[v].append(u)  # reversed edges
    
    visited = set()
    finish_stack = []
    
    def dfs1(node):
        visited.add(node)
        for nxt in graph[node]:
            if nxt not in visited:
                dfs1(nxt)
        finish_stack.append(node)  # push after all neighbours done
    
    for i in range(n):
        if i not in visited:
            dfs1(i)
    
    return finish_stack, rev_graph

パス2:転置グラフでのDFS

完了順のスタックからノードをポップし(完了時刻が最も大きい順)、転置グラフに対してDFSを実行します。未訪問のノードから開始する各DFSは、正確に1つのSCCを見つけます。このDFSで到達したすべてのノードに、同じ成分に属するという印を付けます。

from collections import defaultdict

def kosaraju_full(n, edges):
    graph = defaultdict(list)
    rev_graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
        rev_graph[v].append(u)
    
    visited = set()
    finish_stack = []
    
    def dfs1(node):
        visited.add(node)
        for nxt in graph[node]:
            if nxt not in visited: dfs1(nxt)
        finish_stack.append(node)
    
    for i in range(n):
        if i not in visited: dfs1(i)
    
    visited.clear()
    sccs = []
    
    def dfs2(node, component):
        visited.add(node)
        component.append(node)
        for nxt in rev_graph[node]:
            if nxt not in visited: dfs2(nxt, component)
    
    while finish_stack:
        node = finish_stack.pop()
        if node not in visited:
            component = []
            dfs2(node, component)
            sccs.append(component)
    
    return sccs

# Graph with SCCs: {0,1,2} and {3}
edges = [(0,1),(1,2),(2,0),(1,3)]
print(kosaraju_full(4, edges))  # [[3], [0,2,1]] or similar

グラフの転置

転置グラフでは、すべてのエッジの向きを反転します。元のグラフにu → vがある場合、転置グラフにはv → uがあります。転置してもSCCは保持されます。元のグラフでAとBが同じSCCに属している場合、すべての経路が反転しても互いに接続されたままなので、転置グラフでも同じSCCに属します。上で示したように入力の解析中に転置グラフを構築すれば、別途転置する手順を省略できます。

大規模グラフ向けの反復版

大規模なグラフでは、Pythonの再帰制限を回避するため、再帰的DFSを明示的なスタックを使う反復的DFSに置き換えます。反復版ではノードをスタックに積んで処理し、帰りがけ順を再現するために別の「return」マーカーを保持します。

def dfs1_iterative(start, graph, visited, finish_stack):
    stack = [(start, iter(graph[start]))]
    visited.add(start)
    while stack:
        node, neighbours = stack[-1]
        try:
            nxt = next(neighbours)
            if nxt not in visited:
                visited.add(nxt)
                stack.append((nxt, iter(graph[nxt])))
        except StopIteration:
            stack.pop()
            finish_stack.append(node)

print('Iterative DFS for large graphs avoids recursion limit')

Tarjan法:SCCの別解

Tarjanのアルゴリズムは、Kosaraju法の2回の走査と比較して、1回のDFS走査でSCCを見つけます。ノードのスタックを保持し、各ノードに発見時刻とローリンク値を割り当てます。あるノードの発見時刻とローリンク値が等しい場合、そのノードはSCCのルートです。Tarjan法は実装がやや複雑ですが、転置グラフを構築する必要がありません。どちらもO(V + E)です。

SCCの応用

SCCは次のような用途で使用されます。(1) コンパイラの最適化 — 相互再帰する関数の特定。(2) ソーシャルネットワーク分析 — 密接に結び付いたコミュニティの発見。(3) 2-SAT問題 — 2リテラル節の充足可能性の判定。(4) ウェブクローリング — 相互リンクが密なページ群の特定。(5) 縮約DAG — SCCを見つけた後、グラフの縮約はDAGになるため、閉路を含むグラフに対してトポロジカルな分析を行えます。

縮約DAG

有向グラフの縮約では、各SCCを1つのノードにまとめ、構成要素であるSCC間に辺が存在する場合に、2つのスーパー ノードの間に辺を追加します。結果は常にDAGになるため、トポロジカルソートを実行できます。これにより、DAGでのみ機能するアルゴリズム(DPなど)を、縮約上で処理することで一般の有向グラフに適用できます。

def build_condensation(n, edges, sccs):
    # Assign each node to its SCC index
    scc_id = [0] * n
    for idx, component in enumerate(sccs):
        for node in component:
            scc_id[node] = idx
    
    # Build condensation edges
    condensation_edges = set()
    for u, v in edges:
        su, sv = scc_id[u], scc_id[v]
        if su != sv:
            condensation_edges.add((su, sv))
    
    return list(condensation_edges)

edges = [(0,1),(1,2),(2,0),(1,3)]
sccs = [[3],[0,1,2]]
print(build_condensation(4, edges, sccs))  # [(0,1)] or [(1,0)]

SCCの数とグラフの性質

有向グラフに含まれるSCCの数から、そのグラフの閉路構造が分かります。DAGにはn個のSCCがあります(各ノードがそれぞれ独立したSCCです)。強連結グラフにはSCCがちょうど1個あります。一般に、SCCを縮約すると、それらはDAGを形成します。これが縮約です。縮約DAGに一意なソース(入次数0のノード)と一意なシンク(出次数0のノード)がある場合、特定の連結性が成り立ちます。これらの性質は、最小限の辺を追加した後の到達可能性を問う問題で検証されます。

理解度チェック

このレッスンで扱った Data Structures & Algorithms — Coding Interview Prep の概念について、理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、SCCは、すべてのノードから他のすべてのノードへ到達できる極大集合であること、Kosaraju法は2回のDFS走査を使用し、最初は元のグラフで完了順を求め、次に転置グラフを走査すること、そして任意の有向グラフを縮約するとDAGになり、さらなる分析に利用できることを学びました。次は、挿入、検索、プレフィックス操作のためのTrieNodeデータ構造を構築します。

よくある質問

「Kosarajuによる強連結成分」レッスンは無料ですか?

はい。「Kosarajuによる強連結成分」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、DSA Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 DSA Interview Prepコースには全4レッスンが含まれています。

「Kosarajuによる強連結成分」で何を学びますか?

元のグラフでDFSを実行して終了順を取得し、グラフを転置してから終了順の逆順にもう一度DFSを実行し、SCCを特定します。 ブラウザで直接実行するハンズオンコードでDSA Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

DSA Interview Prepを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのDSA Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「Kosarajuによる強連結成分」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このDSA Interview Prepレッスンでコードを書いて実行できますか?

はい。すべてのDSA Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Kahnのアルゴリズム:BFSトポロジカルソート
  2. DFSの後順によるトポロジカルソート
  3. Course Schedule IとII
  4. Kosarajuによる強連結成分
← DSA Interview Prepに戻る