Até agora, tratamos grafos onde as relações são simétricas: se \(A\) está conectado a \(B\), então \(B\) também está conectado a \(A\). Mas muitas relações do mundo real são assimétricas.
Uma rua de mão única vai de \(A\) para \(B\), mas não de \(B\) para \(A\). Um hyperlink leva de uma página a outra, mas a outra página não necessariamente aponta de volta. Uma tarefa \(A\) precisa ser concluída antes de \(B\) começar — não o contrário.
Para modelar essas situações, precisamos dos grafos direcionados.
Por que Dígrafos Importam? #
Sempre que uma relação entre dois objetos não é automaticamente recíproca, um grafo comum não basta — é preciso registrar também o sentido dela. Alguns exemplos:
- Navegação e mapas: ruas de mão única e rotas aéreas nem sempre podem ser percorridas nos dois sentidos
- Web e mecanismos de busca: hyperlinks apontam de uma página para outra, e o PageRank do Google — já visto em Grafos: Uma Linguagem para Modelar Conexões — é construído sobre essa estrutura
- Redes sociais: seguir alguém no Instagram ou no X não implica ser seguido de volta
- Compiladores e gerenciadores de pacotes: dependências entre módulos só podem ser resolvidas em uma ordem específica
- Escalonamento de tarefas: um projeto com etapas que precisam acontecer em sequência
Para tornar a ideia concreta antes da definição formal, considere o seguinte cenário.
Exemplo: um quarteirão com mão única
Enunciado: Considere um quarteirão de Copacabana, no Rio de Janeiro, limitado por quatro vias — a Avenida Atlântica, a Rua Figueiredo Magalhães, a Avenida Nossa Senhora de Copacabana e a Rua Santa Clara — com a Rua Domingos Ferreira cortando o quarteirão ao meio. Modele esse quarteirão primeiro como um grafo \(G\) comum e depois como um dígrafo \(D\), registrando o sentido do trânsito de cada rua.
Solução: Cada esquina (cruzamento de ruas) é um vértice, identificado pelas duas ruas que se cruzam ali: \(AF\) (Atlântica × Figueiredo), \(AS\) (Atlântica × Santa Clara), \(DF\) (Domingos Ferreira × Figueiredo), \(DS\) (Domingos Ferreira × Santa Clara), \(NF\) (N. S. Copacabana × Figueiredo) e \(NS\) (N. S. Copacabana × Santa Clara). Cada trecho de rua entre duas esquinas vizinhas é uma aresta.
Sem considerar o sentido do trânsito, isso já é um grafo \(G\) comum, com arestas
$$(AF,AS), (AF,DF), (DF,DS), (DF,NF), (DS,AS), (NS,DS), (NF,NS)$$Mas as ruas têm sentido de tráfego: a Av. Atlântica tem mão dupla; a Rua Figueiredo Magalhães é de mão única no sentido \(AF \to DF \to NF\); a Rua Santa Clara tem mão única no sentido oposto, \(NS \to DS\) e \(DS \to AS\); a Rua Domingos Ferreira flui de \(DF\) para \(DS\); e a Av. Nossa Senhora de Copacabana flui de \(NS\) para \(NF\). Registrando cada sentido como um arco, obtemos o dígrafo
$$D: \{(AF,AS), (AS,AF), (AF,DF), (DF,NF), (NS,DS), (DS,AS), (DF,DS), (NS,NF)\}$$— o mesmo quarteirão, agora com a informação de para onde o trânsito pode seguir em cada rua. A figura abaixo mostra as duas versões lado a lado:

O Que É um Dígrafo? #
Um dígrafo \(D = (V, E)\) é um par onde:
- \(V\) é um conjunto finito não vazio de vértices
- \(E\) é um conjunto de arcos: pares ordenados de vértices distintos
Para um arco \((v, w) \in E(D)\):
- \(v\) é a cauda (ponto de partida)
- \(w\) é a cabeça (ponto de chegada)
- Dizemos que o arco é divergente de \(v\) e convergente a \(w\)
- Na representação geométrica, desenhamos uma seta de \(v\) para \(w\)
Em um grafo (não direcionado), a aresta \(\{u,v\} = \{v,u\}\) — não há distinção.
Em um dígrafo, o arco \((u,v) \neq (v,u)\) — são arcos diferentes! Ambos podem existir simultaneamente.

Exemplo concreto
Enunciado: Para o dígrafo \(D\) com \(V = \{a, b, c, d, e\}\) e \(E = \{(a,b),(b,c),(a,e),(b,e),(e,b),(e,c),(d,c),(d,e)\}\), descreva as relações de adjacência de cada vértice.
Solução:

- \(a\): aponta para \(b\) e \(e\) (mas ninguém aponta para \(a\))
- \(b\) e \(e\) têm arcos em ambas as direções entre si: \((b,e)\) e \((e,b)\)
- \(c\): recebe arcos de \(b\), \(e\) e \(d\), mas não aponta para ninguém
Grafo Subjacente e Orientação #
O grafo subjacente de um dígrafo \(D\) é obtido removendo as direções dos arcos (transformando-os em arestas não direcionadas) e eliminando arestas paralelas resultantes.
Exemplo: construindo o grafo subjacente
Enunciado: Construa o grafo subjacente do dígrafo \(D\) do exemplo anterior (\(V=\{a,b,c,d,e\}\), arcos \(\{(a,b),(b,c),(a,e),(b,e),(e,b),(e,c),(d,c),(d,e)\}\)).
Solução: O grafo subjacente é construído em duas etapas:
- Remover as direções: cada arco vira uma aresta comum. Como \((b,e)\) e \((e,b)\) eram dois arcos distintos entre os mesmos vértices, viram duas arestas paralelas entre \(b\) e \(e\).
- Remover as arestas paralelas: as duas arestas entre \(b\) e \(e\) colapsam em uma só.
O resultado é o grafo subjacente \(G\), com arestas \((a,b), (a,e), (b,c), (b,e), (e,c), (c,d), (d,e)\) — 7 arestas, uma a menos que os 8 arcos originais de \(D\). A figura abaixo mostra as duas etapas:

Uma orientação de um grafo \(G\) é um dígrafo \(\vec{G}\) obtido especificando uma direção para cada aresta de \(G\). A orientação de um grafo não é única — o mesmo \(G\) admite várias orientações diferentes.
Exemplo: duas orientações diferentes do mesmo grafo
Enunciado: Encontre duas orientações diferentes do grafo subjacente \(G\) do exemplo anterior.
Solução: Duas possibilidades:
- \(\vec{G}\): \((a,b), (a,e), (b,c), (e,b), (c,e), (c,d), (e,d)\)
- \((\vec{G})’\): a orientação oposta de cada aresta — \((b,a), (e,a), (c,b), (b,e), (e,c), (d,c), (d,e)\)
As duas são dígrafos válidos sobre o mesmo grafo subjacente \(G\), mas com arcos apontando em sentidos diferentes — por isso a orientação de um grafo não é única, como mostra a figura:

Graus de Entrada e Saída #
Em dígrafos, cada vértice tem dois graus:
Para um vértice \(v\) em um dígrafo \(D\):
- Grau de entrada: \(d^-(v) = |\{(u,v) \in E(D)\}|\) — número de arcos que chegam a \(v\)
- Grau de saída: \(d^+(v) = |\{(v,u) \in E(D)\}|\) — número de arcos que partem de \(v\)
Vértices especiais:
- Fonte: \(d^-(v) = 0\) — nenhum arco chega a \(v\)
- Sumidouro: \(d^+(v) = 0\) — nenhum arco parte de \(v\)
Fórmula Análoga ao Aperto de Mãos #
Prova: Cada arco \((u,v)\) contribui 1 para \(d^+(u)\) e 1 para \(d^-(v)\). Somando sobre todos os arcos, cada soma conta cada arco exatamente uma vez. Portanto, \(\sum d^-(v) = \sum d^+(v) = |E(D)|\), que é o que queríamos demonstrar. \(\blacksquare\)
Exemplo: graus no dígrafo acima
Enunciado: Calcule o grau de entrada e o grau de saída de cada vértice do dígrafo \(D\) do exemplo acima (\(V = \{a,b,c,d,e\}\)), e identifique fontes e sumidouros.
Solução:
| Vértice | \(d^+(v)\) | \(d^-(v)\) |
|---|---|---|
| \(a\) | 2 | 0 |
| \(b\) | 2 | 2 |
| \(c\) | 0 | 3 |
| \(d\) | 2 | 0 |
| \(e\) | 2 | 3 |
\(a\) e \(d\) são fontes; \(c\) é sumidouro.
Soma dos graus de saída: \(2+2+0+2+2 = 8 = |E|\). ✓
Conferindo com código
Contar arcos à mão funciona para dígrafos pequenos, mas o networkx
já traz o grau de entrada e de saída prontos como métodos do próprio
dígrafo: D.in_degree(v) e D.out_degree(v). O código abaixo percorre
cada vértice, calcula os dois graus e marca quem é fonte (\(d^-=0\)) ou
sumidouro (\(d^+=0\)):
import networkx as nx
D = nx.DiGraph([("a", "b"), ("b", "c"), ("a", "e"), ("b", "e"),
("e", "b"), ("e", "c"), ("d", "c"), ("d", "e")])
for v in sorted(D.nodes()):
d_mais, d_menos = D.out_degree(v), D.in_degree(v)
papel = " (fonte)" if d_menos == 0 else " (sumidouro)" if d_mais == 0 else ""
print(f"{v}: d+={d_mais}, d-={d_menos}{papel}")a: d+=2, d-=0 (fonte)
b: d+=2, d-=2
c: d+=0, d-=3 (sumidouro)
d: d+=2, d-=0 (fonte)
e: d+=2, d-=3O resultado bate exatamente com a tabela calculada à mão.
Caminhos e Ciclos em Dígrafos #
As definições de passeio, trajeto, caminho e ciclo, já vistas em Caminhos, Ciclos e Conexidade, se mantêm, mas agora com uma restrição importante: deve-se seguir as direções dos arcos.
Um passeio direcionado em um dígrafo \(D\) é uma sequência de vértices \(P = v_0, v_1, \ldots, v_k\) tal que \((v_i, v_{i+1}) \in E(D)\) para \(0 \leq i \leq k-1\) — ou seja, cada passo da sequência segue um arco de \(D\) na direção indicada. Vértices e arcos podem se repetir.
Um trajeto direcionado é um passeio direcionado em que nenhum arco se repete (mas vértices podem se repetir).
Um caminho direcionado é um passeio direcionado em que nenhum vértice se repete (e portanto nenhum arco também se repete).
Exemplo: passeio, trajeto e caminho no mesmo dígrafo
Enunciado: Considere o dígrafo com \(V = \{a,b,c,d,e,f,g,h\}\) e arcos \(E = \{(a,b),(b,c),(b,e),(c,e),(c,g),(e,a),(e,d),(d,a),(d,c),(d,f),(f,d),(d,h)\}\), mostrado na figura abaixo. Classifique as sequências \(P_1 = a,b,c,e,a,b,c,g\), \(P_2 = a,b,e,d,f,d,c,g\) e \(P_3 = a,b,c,g\).

Solução:
- \(P_1\) é um passeio direcionado de \(a\) a \(g\): os arcos \((a,b)\) e \((b,c)\) são percorridos duas vezes — logo não é um trajeto.
- \(P_2\) é um trajeto direcionado de \(a\) a \(g\): nenhum arco se repete, mas o vértice \(d\) aparece duas vezes — logo não é um caminho.
- \(P_3\) é um caminho direcionado de \(a\) a \(g\): nenhum vértice se repete.
Além disso, \(C_1 = a,b,c,e,d,a\) é um ciclo direcionado, e \(C_2 = d,f,d\) é um ciclo direcionado de comprimento 2 — possível porque \((d,f)\) e \((f,d)\) são ambos arcos de \(D\).
O ciclo \(C_2 = d,f,d\) do exemplo acima ilustra um fato exclusivo de dígrafos: se \((u,v)\) e \((v,u)\) são ambos arcos, então \(u \to v \to u\) é um ciclo de comprimento 2. Em grafos simples (não direcionados), isso não acontece, pois a mesma aresta não pode ser percorrida duas vezes em um ciclo.
Alcançabilidade: Dizemos que \(v\) alcança \(u\) se existe um caminho direcionado de \(v\) a \(u\).
Exemplo: alcançabilidade
Enunciado: No dígrafo da figura anterior, quais vértices \(a\) alcança? Existe algum vértice que não alcança \(a\)?
Solução: \(a\) alcança \(g\) (via \(a,b,c,g\)), alcança \(f\) (via \(a,b,e,d,f\)) e alcança \(h\) (via \(a,b,e,d,h\)) — na verdade, \(a\) alcança todos os outros vértices.
Isso não vale para todo vértice: \(g\) não tem nenhum arco saindo dele, então \(g\) não alcança ninguém — em particular, \(g\) não alcança \(a\).
Conexidade em Dígrafos #
A noção de conexidade, vista em Caminhos, Ciclos e Conexidade para grafos comuns, é mais rica em dígrafos, pois as direções importam:
\(D\) é fortemente conexo se para todo par \(v, w \in V\) existe um caminho dirigido de \(v\) para \(w\) e de \(w\) para \(v\).
\(D\) é unilateralmente conexo se para todo par \(v, w \in V\) existe caminho dirigido de \(v\) para \(w\) ou de \(w\) para \(v\) (pelo menos um dos dois).
\(D\) é fracamente conexo se o grafo subjacente é conexo (ignorando as direções).
\(D\) é desconexo se o grafo subjacente é desconexo.
Hierarquia de Conexidade #
$$\text{Fortemente conexo} \implies \text{Unilateralmente conexo} \implies \text{Fracamente conexo}$$As implicações são estritas: cada nível é genuinamente mais fraco, como mostram os exemplos a seguir.
Exemplo: fortemente conexo
Enunciado: Verifique que o dígrafo \(D\) com \(V=\{a,b,c,d,e\}\) e arcos \(E = \{(a,b),(b,c),(c,d),(d,a),(a,e),(c,e),(e,b),(e,d)\}\), mostrado abaixo, é fortemente conexo.

Solução: Basta mostrar que, partindo de qualquer vértice, chega-se a todos os outros. Por exemplo, partindo de \(a\): chega-se a \(e\) diretamente, a \(b\) via \(a,b\), a \(c\) via \(a,b,c\) e a \(d\) via \(a,e,d\) (ou \(a,b,c,d\)). Repetindo essa verificação a partir de cada vértice, confirma-se que \(D\) é fortemente conexo.
Exemplo: os três níveis de conexidade lado a lado
Enunciado: Classifique os três dígrafos abaixo, todos com \(V = \{a,b,c,d,e\}\), quanto ao nível de conexidade:
- \(D_1\): arcos \((a,b), (d,a), (b,c), (d,c), (c,e)\).
- \(D_2\): arcos \((a,b), (a,d), (c,b), (c,d), (c,e)\).
- \(D_3\): arcos \((a,b), (d,a), (b,c), (d,c)\), com \(e\) isolado.

Solução:
- Em \(D_1\), \(d\) é uma fonte que alcança todos os demais (\(d \to a \to b \to c \to e\)), e \(e\) é um sumidouro alcançado por todos os demais. Como \(a\), \(b\) e \(c\) formam uma cadeia direta \(a \to b \to c\), todo par de vértices tem caminho em pelo menos um sentido — \(D_1\) é unilateralmente conexo. Mas como nada chega a \(d\) e \(e\) não alcança ninguém, \(D_1\) não é fortemente conexo.
- Em \(D_2\), \(a\) só alcança \(b\) e \(d\), enquanto \(c\) alcança \(b\), \(d\) e \(e\) — nenhum dos dois alcança o outro. Esse único par já basta para \(D_2\) não ser unilateralmente conexo. Mas o grafo subjacente é conexo (todos os vértices se ligam através de \(a\) e \(c\)), então \(D_2\) é fracamente conexo.
- Em \(D_3\), o vértice \(e\) está isolado — nenhum arco o liga aos demais, nem mesmo sem considerar direção. Como o grafo subjacente já é desconexo, \(D_3\) é desconexo.
Conferindo com código
Verificar conexidade à mão exige checar caminhos entre vários pares de
vértices — cansativo mesmo em dígrafos pequenos. O networkx tem uma
função pronta para cada um dos três níveis: nx.is_strongly_connected,
nx.is_semiconnected (esse é o nome técnico para “unilateralmente
conexo”) e nx.is_weakly_connected:
import networkx as nx
D1 = nx.DiGraph([("a", "b"), ("d", "a"), ("b", "c"), ("d", "c"), ("c", "e")])
D2 = nx.DiGraph([("a", "b"), ("a", "d"), ("c", "b"), ("c", "d"), ("c", "e")])
D3 = nx.DiGraph([("a", "b"), ("d", "a"), ("b", "c"), ("d", "c")])
D3.add_node("e") # e isolado
for nome, D in [("D1", D1), ("D2", D2), ("D3", D3)]:
print(f"{nome}: forte={nx.is_strongly_connected(D)}, "
f"unilateral={nx.is_semiconnected(D)}, "
f"fraco={nx.is_weakly_connected(D)}")D1: forte=False, unilateral=True, fraco=True
D2: forte=False, unilateral=False, fraco=True
D3: forte=False, unilateral=False, fraco=FalseRepare que fraco=True para \(D_1\) também — faz sentido, já que
unilateralmente conexo implica fracamente conexo (a hierarquia vista
antes). Só \(D_3\) tem fraco=False, exatamente porque \(e\) está
isolado.
Um dígrafo que não é fortemente conexo se divide em componentes fortemente
conexos: os maiores conjuntos de vértices em que cada um alcança todos os outros.
No dígrafo dos exemplos de passeio, eles são \(\{a,b,c,d,e,f\}\),
\(\{g\}\) e \(\{h\}\). Encontrá-los é um problema clássico, resolvido em
tempo linear, \(O(n + m)\), por dois algoritmos baseados em busca em
profundidade: o de Robert Tarjan, publicado em 1972, e o de S. Rao Kosaraju,
descrito em 1978 e publicado por Micha Sharir em 1981 (veja o verbete
Strongly connected component).
No networkx, a função é nx.strongly_connected_components:
import networkx as nx
D = nx.DiGraph([
("a", "b"), ("b", "c"), ("b", "e"), ("c", "e"), ("c", "g"), ("e", "a"),
("e", "d"), ("d", "a"), ("d", "c"), ("d", "f"), ("f", "d"), ("d", "h"),
])
print(sorted(sorted(c) for c in nx.strongly_connected_components(D)))
# Saída: [['a', 'b', 'c', 'd', 'e', 'f'], ['g'], ['h']]Representação Matricial #
Matriz de Adjacência de Dígrafo #
Para \(D\) com \(V = \{v_1, \ldots, v_n\}\), a matriz de adjacência \(A = (a_{ij})\) é \(n \times n\):
$$a_{ij} = \begin{cases} 1 & \text{se } (v_i, v_j) \in E(D) \\ 0 & \text{caso contrário} \end{cases}$$A matriz de adjacência de um dígrafo não é simétrica em geral — ao contrário de grafos não orientados.
Relação com graus:
- Soma da linha \(i\): fornece \(d^+(v_i)\) (grau de saída)
- Soma da coluna \(j\): fornece \(d^-(v_j)\) (grau de entrada)
Exemplo: matriz de adjacência
Enunciado: Construa a matriz de adjacência do dígrafo \(D\) com \(V = \{v_1,v_2,v_3,v_4\}\) e arcos \((v_1,v_2), (v_1,v_4), (v_3,v_2), (v_3,v_4), (v_4,v_2), (v_2,v_4)\), e identifique fontes e sumidouros a partir dela.

Solução:
$$A = \begin{array}{c|cccc} & v_1 & v_2 & v_3 & v_4 \\ \hline v_1 & 0 & 1 & 0 & 1 \\ v_2 & 0 & 0 & 0 & 1 \\ v_3 & 0 & 1 & 0 & 1 \\ v_4 & 0 & 1 & 0 & 0 \end{array}$$- Linha de \(v_1\): soma = 2 = \(d^+(v_1)\)
- Coluna de \(v_2\): soma = \(1+0+1+1 = 3 = d^-(v_2)\)
- Colunas de \(v_1\) e \(v_3\) são totalmente nulas: \(v_1\) e \(v_3\) são fontes (nenhum arco chega a eles)
- Nenhuma linha é totalmente nula: \(D\) não tem sumidouro
Matriz de Incidência de Dígrafo #
Para \(D\) com \(n\) vértices e \(m\) arcos, a matriz de incidência \(B = (b_{ij})\) é \(n \times m\):
$$b_{ij} = \begin{cases} +1 & \text{se o arco } e_j \text{ diverge de } v_i \text{ (cauda)} \\ -1 & \text{se o arco } e_j \text{ converge a } v_i \text{ (cabeça)} \\ 0 & \text{caso contrário} \end{cases}$$Cada coluna tem exatamente um \(+1\) (a cauda do arco) e um \(-1\) (a cabeça do arco).
Exemplo: matriz de incidência
Enunciado: Construa a matriz de incidência do mesmo dígrafo \(v_1,v_2,v_3,v_4\) do exemplo anterior, agora com os arcos rotulados \(e_1=(v_1,v_2), e_2=(v_3,v_2), e_3=(v_3,v_4), e_4=(v_1,v_4), e_5=(v_4,v_2), e_6=(v_2,v_4)\):

Solução:
$$B = \begin{array}{c|cccccc} & e_1 & e_2 & e_3 & e_4 & e_5 & e_6 \\ \hline v_1 & 1 & 0 & 0 & 1 & 0 & 0 \\ v_2 & -1 & -1 & 0 & 0 & -1 & 1 \\ v_3 & 0 & 1 & 1 & 0 & 0 & 0 \\ v_4 & 0 & 0 & -1 & -1 & 1 & -1 \end{array}$$Cada coluna tem exatamente um \(+1\) e um \(-1\): por exemplo, a coluna de \(e_5\) tem \(+1\) em \(v_4\) e \(-1\) em \(v_2\), confirmando o arco \((v_4,v_2)\).
Conferindo com código
O networkx calcula as duas matrizes prontas: nx.to_numpy_array para a
de adjacência e nx.incidence_matrix para a de incidência. Só um
detalhe: a convenção de sinal do networkx é a oposta da nossa
(\(-1\) na cauda e \(+1\) na cabeça, em vez de \(+1\) na cauda e
\(-1\) na cabeça) — então basta multiplicar o resultado por \(-1\)
para bater com a matriz \(B\) que calculamos à mão:
import networkx as nx
D = nx.DiGraph()
D.add_edges_from([("v1", "v2"), ("v1", "v4"), ("v3", "v2"),
("v3", "v4"), ("v4", "v2"), ("v2", "v4")])
vertices = ["v1", "v2", "v3", "v4"]
arcos = [("v1", "v2"), ("v3", "v2"), ("v3", "v4"), ("v1", "v4"), ("v4", "v2"), ("v2", "v4")] # e1..e6
A = nx.to_numpy_array(D, nodelist=vertices, dtype=int)
print("Matriz de adjacência:\n", A)
B = -nx.incidence_matrix(D, nodelist=vertices, edgelist=arcos, oriented=True).toarray().astype(int)
print("Matriz de incidência:\n", B)Matriz de adjacência:
[[0 1 0 1]
[0 0 0 1]
[0 1 0 1]
[0 1 0 0]]
Matriz de incidência:
[[ 1 0 0 1 0 0]
[-1 -1 0 0 -1 1]
[ 0 1 1 0 0 0]
[ 0 0 -1 -1 1 -1]]As duas batem, arco por arco, com as matrizes \(A\) e \(B\) calculadas nos exemplos acima.
Aplicações de Dígrafos #
Com a base teórica estabelecida, vale destacar como os dígrafos aparecem na prática, muitas vezes como parte central de outras estruturas:
- Fluxo em redes: água em tubulações com válvulas, corrente elétrica em circuitos — o sentido do fluxo é parte do modelo
- World Wide Web: páginas são vértices e hyperlinks são arcos; o PageRank (detalhado em Grafos: Uma Linguagem para Modelar Conexões) usa os graus de entrada e saída desse dígrafo gigante para estimar a relevância de cada página
- Grafos de chamada: em análise de software, quais funções chamam quais — um arco \((f,g)\) significa que \(f\) chama \(g\)
- DAGs (grafos acíclicos direcionados): dígrafos sem nenhum ciclo direcionado, usados para modelar dependências
Um DAG (Directed Acyclic Graph, ou grafo acíclico direcionado) é um dígrafo acíclico (sem ciclos direcionados). É fundamental em:
- Escalonamento de tarefas com dependências (o arco \((A, B)\) significa “A antes de B”), como no Apache Airflow
- Instalação de pacotes com dependências (npm, pip, apt)
- Análise de dependências em Makefiles
- Representação de genealogias
Ordenação Topológica #
Num DAG de dependências, a pergunta prática é: em que ordem executar as tarefas, de modo que nenhuma rode antes das que ela precisa? A resposta é uma ordenação topológica.
Uma ordenação topológica de um dígrafo \(D\) é uma lista de todos os seus vértices em que, para todo arco \((u, v)\), \(u\) aparece antes de \(v\).
Um dígrafo admite uma ordenação topológica se e somente se é um DAG.
Prova: (\(\Rightarrow\)) Suponha que \(D\) tenha uma ordenação topológica e também um ciclo direcionado. Entre os vértices do ciclo, seja \(v\) o que aparece primeiro na ordenação. O arco do ciclo que chega a \(v\) sai de um vértice que aparece depois de \(v\) — o que a ordenação proíbe. Logo \(D\) não tem ciclos.
(\(\Leftarrow\)) Primeiro, todo DAG tem uma fonte. Se nenhum vértice fosse fonte, poderíamos partir de qualquer vértice e andar sempre para trás, por um arco que chega ao vértice atual; como há finitos vértices, algum se repetiria, e o trecho entre as duas visitas seria um ciclo direcionado. Agora, por indução no número de vértices: coloque uma fonte \(s\) no início da lista; o dígrafo \(D - s\) continua sem ciclos e, pela hipótese de indução, tem uma ordenação topológica, que vem depois de \(s\). Nenhum arco chega a \(s\), então nenhum arco é violado.
Portanto, um dígrafo admite uma ordenação topológica se e somente se é um DAG, que é o que queríamos demonstrar. \(\blacksquare\)
A segunda metade da prova já é um algoritmo, descrito por Arthur B. Kahn em 1962: tire uma fonte, depois outra, e assim por diante. Para não procurar fontes do zero a cada passo, o algoritmo de Kahn guarda o grau de entrada de cada vértice e uma fila com as fontes da vez; ao retirar um vértice \(u\), diminui em 1 o grau de entrada de cada vértice para o qual \(u\) aponta, e quem chega a 0 entra na fila. Se a fila esvaziar antes de todos os vértices saírem, sobrou um ciclo. Cada vértice e cada arco são tratados uma vez: tempo \(O(n + m)\) (veja o verbete Topological sorting).
from collections import deque
import networkx as nx
tarefas = nx.DiGraph([
("extrair", "limpar"), ("limpar", "treinar"), ("treinar", "avaliar"),
("limpar", "graficos"), ("avaliar", "relatorio"), ("graficos", "relatorio"),
])
def ordem_topologica(D):
"""Algoritmo de Kahn: uma ordenação topológica de D, ou None se D tiver ciclo."""
grau_entrada = {v: D.in_degree(v) for v in D}
fila = deque(v for v in D if grau_entrada[v] == 0) # as fontes
ordem = []
while fila:
u = fila.popleft()
ordem.append(u)
for w in D.successors(u):
grau_entrada[w] -= 1
if grau_entrada[w] == 0:
fila.append(w)
return ordem if len(ordem) == D.number_of_nodes() else None
print(ordem_topologica(tarefas))
# Saída: ['extrair', 'limpar', 'treinar', 'graficos', 'avaliar', 'relatorio']
print(list(nx.topological_sort(tarefas)))
# Saída: ['extrair', 'limpar', 'treinar', 'graficos', 'avaliar', 'relatorio']
tarefas.add_edge("relatorio", "extrair") # fecha um ciclo
print(ordem_topologica(tarefas)) # NoneUm DAG costuma ter várias ordenações topológicas — aqui, graficos poderia vir antes
de treinar —, e qualquer uma delas é uma ordem válida de execução. A figura abaixo
mostra o DAG do exemplo e, embaixo, os mesmos vértices em fila, na ordem encontrada:

Aplicação: Apache Airflow
O Apache Airflow é a ferramenta mais usada
para orquestrar pipelines de dados, e modela cada pipeline literalmente
como um DAG: cada tarefa é um vértice, e o operador >> declara um arco
de dependência entre duas tarefas.
from datetime import datetime
from airflow import DAG
from airflow.operators.python import PythonOperator
def extrair_dados():
print("extraindo os dados de vendas")
def transformar_dados():
print("transformando os dados")
def carregar_dados():
print("carregando os dados no banco")
with DAG("pipeline_vendas", start_date=datetime(2026, 1, 1), schedule="@daily") as dag:
extrair = PythonOperator(task_id="extrair_dados", python_callable=extrair_dados)
transformar = PythonOperator(task_id="transformar_dados", python_callable=transformar_dados)
carregar = PythonOperator(task_id="carregar_dados", python_callable=carregar_dados)
extrair >> transformar >> carregar # arcos do DAG: extrair -> transformar -> carregarTraduzindo para a linguagem deste artigo: cada PythonOperator é um
vértice (uma tarefa que executa uma função Python); o bloco with DAG(...) declara o dígrafo em si, com um nome e uma frequência de
execução; e a linha extrair >> transformar >> carregar declara os
arcos \((extrair, transformar)\) e \((transformar, carregar)\),
na ordem em que as tarefas precisam rodar. Na hora de executar, o Airflow segue
uma ordenação topológica desse DAG.
Se alguém tentasse fechar o ciclo com carregar >> extrair, o Airflow
recusaria o DAG na validação — a aciclicidade não é só uma curiosidade
teórica, é uma regra que a ferramenta impõe.
Comparação: Grafo vs. Dígrafo #
| Característica | Grafo | Dígrafo |
|---|---|---|
| Arestas/Arcos | Pares não ordenados \(\{u,v\}\) | Pares ordenados \((u,v)\) |
| Grau | \(d(v)\) (único) | \(d^+(v)\) (saída) e \(d^-(v)\) (entrada) |
| Vértices especiais | — | Fonte (\(d^- = 0\)), Sumidouro (\(d^+ = 0\)) |
| Matriz de adjacência | Simétrica | Não necessariamente simétrica |
| Conexidade | Conexo / Desconexo | Forte / Unilateral / Fraco / Desconexo |
| Aperto de mãos | \(\sum d(v) = 2|E|\) | \(\sum d^+(v) = \sum d^-(v) = |E|\) |
Tabela-Resumo #
| Conceito | Expressão/Definição |
|---|---|
| Dígrafo | \(D = (V,E)\), com \(E\) formado por pares ordenados (arcos) |
| Grau de entrada / saída | \(d^-(v) = |\{(u,v) \in E(D)\}|\), \(d^+(v) = |\{(v,u) \in E(D)\}|\) |
| Fonte / Sumidouro | \(d^-(v) = 0\) (fonte) / \(d^+(v) = 0\) (sumidouro) |
| Aperto de mãos para dígrafos | \(\sum_{v} d^-(v) = \sum_{v} d^+(v) = |E(D)|\) |
| Grafo subjacente | \(D\) sem direções, sem arestas paralelas |
| Orientação | \(\vec{G}\): dígrafo obtido escolhendo uma direção para cada aresta de \(G\) (não é única) |
| Fortemente conexo | Todo par \(v,w\) tem caminho de \(v\) a \(w\) e de \(w\) a \(v\) |
| Unilateralmente conexo | Todo par \(v,w\) tem caminho de \(v\) a \(w\) ou de \(w\) a \(v\) |
| Fracamente conexo | O grafo subjacente é conexo |
| Desconexo | O grafo subjacente é desconexo |
| Matriz de adjacência | \(a_{ij} = 1\) se \((v_i,v_j) \in E(D)\), \(0\) caso contrário |
| Matriz de incidência | \(b_{ij} = +1\) (cauda), \(-1\) (cabeça), \(0\) caso contrário |
| Componentes fortemente conexos | Maiores conjuntos de vértices que se alcançam mutuamente; Tarjan e Kosaraju em \(O(n+m)\) |
| Ordenação topológica | Existe \(\iff\) \(D\) é DAG; algoritmo de Kahn em \(O(n+m)\) |
Exercícios #
Exercício 1: Fontes e sumidouros
Para o dígrafo \(D\) com vértices \(\{a,b,c,d\}\) e arcos \(\{(a,b),(a,c),(c,a),(c,b),(d,a),(d,c)\}\):
(a) Quem são as fontes? (b) Quem são os sumidouros? (c) \(a\) alcança todos os vértices? (d) \(D\) é fortemente conexo?
Solução:
(a) Fonte: \(d\) — nenhum arco chega a \(d\) (\(d^-(d) = 0\)).
(b) Sumidouro: \(b\) — nenhum arco parte de \(b\) (\(d^+(b) = 0\)).
(c) De \(a\): \(a \to b\) ✓, \(a \to c\) ✓, mas \(a\) não alcança \(d\). Falso.
(d) Não, pois \(d\) é fonte — nenhum vértice alcança \(d\). Logo \(D\) não é fortemente conexo.
Exercício 2: Conexidade
Classifique como fortemente, unilateralmente ou fracamente conexo:
(a) Cadeia linear \(a \to b \to c \to d \to e \to f\) (b) Ciclo \(a \to b \to c \to a\) com arco extra \(b \to d\)
Solução:
(a) Unilateralmente conexo: de \(a\) alcança-se todos os vértices. Para qualquer par \((v_i, v_j)\) com \(i < j\), existe caminho de \(v_i\) para \(v_j\). Mas \(f\) não alcança \(a\): não é fortemente conexo.
(b) O vértice \(d\) não tem nenhum arco saindo — é sumidouro, então não alcança ninguém. Como \(d\) não alcança nenhum outro vértice, o dígrafo não é fortemente conexo. Mas todo vértice alcança \(d\) (\(a \to b \to d\), \(b \to d\), \(c \to a \to b \to d\)), e entre \(a\), \(b\) e \(c\) o ciclo garante caminho nos dois sentidos — logo todo par tem caminho em pelo menos um sentido, e o dígrafo é unilateralmente conexo.
Resumo Final da Série #
Com os dígrafos, completamos os fundamentos da teoria dos grafos. Recapitulando toda a jornada de “A Matemática das Conexões”:
| Artigo | Tema Central |
|---|---|
| Introdução | História, Königsberg, aplicações |
| Definições | \(G=(V,E)\), subgrafos, \(K_n\), cliques |
| Grau | \(d(v)\), grafos regulares, Aperto de Mãos |
| Isomorfismo | Bijeções que preservam estrutura, matrizes |
| Caminhos | Passeio/trajeto/caminho/ciclo, conexidade, BFS e Dijkstra, bipartido |
| Árvores | Acíclico+conexo, \(m=n-1\), centro, geradora |
| Eulerianos/Hamiltonianos | Percursos especiais, Problema do Caixeiro-Viajante (TSP) |
| Planares | Fórmula de Euler, \(K_5\) e \(K_{3,3}\), Kuratowski, coloração e Quatro Cores |
| Dígrafos | Simetria quebrada, graus orientados, DAGs, ordenação topológica |
Este é o último artigo da série “A Matemática das Conexões”. A teoria dos grafos é uma das áreas mais vivas da matemática contemporânea — conectando abstrações elegantes a problemas práticos que moldam a infraestrutura digital do mundo.