DSA Interview Prep · Lezione

Componenti fortemente connesse con Kosaraju

Esegua la DFS sul grafo originale per ottenere l'ordine di completamento, trasponi il grafo ed esegua nuovamente la DFS nell'ordine inverso di completamento per individuare le SCC.

Lezione 4 di 413 passaggi

Componenti fortemente connesse con Kosaraju è una lezione DSA Interview Prep gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA Interview Prep include 4 lezioni in totale.

Definizione delle componenti fortemente connesse

Una componente fortemente connessa (SCC) di un grafo orientato è un insieme massimale di nodi tale che esiste un percorso da ogni nodo a ogni altro nodo dell'insieme. Ad esempio, se i nodi A, B e C formano un ciclo (A→B→C→A), appartengono tutti alla stessa SCC. Un singolo nodo senza un self-loop costituisce una SCC a sé. Le SCC rivelano la struttura ciclica di un grafo orientato.

Algoritmo di Kosaraju's: due passaggi DFS

L'algoritmo di Kosaraju's trova tutte le SCC in O(V + E) utilizzando due passaggi DFS. Passaggio 1: esegua la DFS sul grafo originale e inserisca i nodi in uno stack secondo il loro ordine di completamento, cioè in post-order. Passaggio 2: esegua la DFS sul grafo trasposto, cioè invertito, elaborando i nodi in ordine inverso rispetto al completamento, estraendoli dallo stack. Ogni albero DFS del passaggio 2 corrisponde a una SCC.

Perché funziona l'algoritmo di Kosaraju's

Nel passaggio 1, la SCC il cui albero DFS termina per ultimo è quella senza archi uscenti verso altre SCC, cioè una SCC "pozzo" nel DAG di condensazione. Nel grafo trasposto, questa SCC non ha archi entranti da altre SCC, quindi la DFS avviata da essa rimane confinata al suo interno durante il passaggio 2. Ogni DFS successiva del passaggio 2 rimane all'interno della propria SCC, perché tutti gli archi tra SCC sono stati invertiti e conducono verso SCC già visitate.

Passaggio 1: costruire l'ordine di completamento

Esegua la DFS sul grafo originale e inserisca ogni nodo in uno stack dopo averlo completato, cioè in post-order. In questo passaggio non ci interessano le componenti, ma solo l'ordine di completamento. L'ultimo nodo a terminare apparterrà a una SCC "sorgente" del DAG di condensazione.

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

Passaggio 2: DFS sul grafo trasposto

Estragga i nodi dallo stack dei completamenti, iniziando dal tempo di completamento maggiore, ed esegua la DFS sul grafo trasposto. Ogni DFS avviata da un nodo non visitato scopre esattamente una SCC. Contrassegni tutti i nodi raggiunti da questa DFS come appartenenti alla stessa componente.

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

Trasporre il grafo

Il grafo trasposto inverte ogni arco: se il grafo originale contiene u → v, il trasposto contiene v → u. La trasposizione preserva le SCC: se A e B appartengono alla stessa SCC nel grafo originale, rimangono nella stessa SCC anche nel trasposto, perché tutti i percorsi vengono invertiti ma continuano a collegare i nodi. Costruire il trasposto durante l'analisi dell'input, come mostrato sopra, evita un passaggio di trasposizione separato.

Versione iterativa per grafi grandi

Per i grafi grandi, si sostituisca la DFS ricorsiva con una DFS iterativa che utilizza uno stack esplicito, così da evitare il limite di ricorsione di Python. La versione iterativa inserisce i nodi nello stack, li elabora e mantiene un marcatore separato di «ritorno» per simulare l'ordine posticipato.

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')

Algoritmo di Tarjan: alternativa per le SCC

Algoritmo di Tarjan trova le SCC in un'unica passata DFS, rispetto alle due passate di Kosaraju. Mantiene uno stack di nodi e assegna a ogni nodo un tempo di scoperta e un valore low-link. Quando il tempo di scoperta di un nodo è uguale al suo low-link, quel nodo è la radice di una SCC. L'algoritmo di Tarjan è leggermente più complesso da implementare, ma evita di costruire il grafo trasposto. Entrambi hanno complessità O(V + E).

Applicazioni delle SCC

Le SCC vengono utilizzate per: (1) Ottimizzazione dei compilatori — individuare le funzioni ricorsive reciproche. (2) Analisi delle reti sociali — trovare comunità strettamente interconnesse. (3) Problema 2-SAT — determinare la soddisfacibilità di clausole con 2 letterali. (4) Web crawling — individuare gruppi di pagine con molti collegamenti incrociati. (5) DAG di condensazione — dopo aver trovato le SCC, la condensazione del grafo è un DAG, che consente di analizzare topologicamente grafi ciclici.

DAG di condensazione

La condensazione di un grafo orientato contrae ogni SCC in un singolo nodo e aggiunge un arco tra due supernodi se esiste un arco tra le rispettive SCC costituenti. Il risultato è sempre un DAG, sul quale è possibile eseguire un ordinamento topologico. Questo consente di applicare ai grafi orientati generali algoritmi che funzionano solo sui DAG, come la programmazione dinamica, lavorando sulla loro condensazione.

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)]

Numero di SCC e proprietà dei grafi

Il numero di SCC in un grafo orientato rivela la sua struttura ciclica. Un DAG ha n SCC, poiché ogni nodo costituisce una SCC distinta. Un grafo fortemente connesso ha esattamente 1 SCC. In generale, dopo la condensazione, le SCC formano un DAG: il grafo di condensazione. Se il DAG di condensazione ha un'unica sorgente, cioè un nodo con grado entrante 0, e un'unico pozzo, cioè un nodo con grado uscente 0, nel grafo di condensazione valgono determinate proprietà di connettività. Queste proprietà vengono verificate nei problemi di raggiungibilità dopo l'aggiunta di un numero minimo di archi.

Verifica rapida

Verifichi la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep trattati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: le SCC sono insiemi massimali in cui ogni nodo è raggiungibile da tutti gli altri, Kosaraju utilizza due passate DFS — la prima sul grafo originale per determinare l'ordine di completamento, la seconda sul grafo trasposto e la condensazione di qualsiasi grafo orientato è un DAG utilizzabile per ulteriori analisi. Nella prossima lezione costruiremo strutture dati TrieNode per le operazioni di inserimento, ricerca e gestione dei prefissi.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Componenti fortemente connesse con Kosaraju» è gratuita?

Sì — il testo completo di «Componenti fortemente connesse con Kosaraju» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Componenti fortemente connesse con Kosaraju»?

Esegua la DFS sul grafo originale per ottenere l'ordine di completamento, trasponi il grafo ed esegua nuovamente la DFS nell'ordine inverso di completamento per individuare le SCC. Eserciti DSA Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare DSA Interview Prep?

Non è richiesta alcuna esperienza precedente. DSA Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Componenti fortemente connesse con Kosaraju»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione DSA Interview Prep?

Sì. Ogni lezione DSA Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Algoritmo di Kahn: ordinamento topologico BFS
  2. Ordinamento topologico DFS post-order
  3. Course Schedule I e II
  4. Componenti fortemente connesse con Kosaraju
← Torna a DSA Interview Prep