Grafos
Redes sociais, mapas, a internet e a grade de disciplinas do curso têm a mesma forma: coisas ligadas a outras coisas. Hoje vamos representar essas ligações no computador e percorrê-las para responder perguntas como “qual o caminho mais curto?” e “em que ordem fazer?”.
Navegação: ← → trocam de etapa · V alterna entre etapas e página única · M sumário · F tela cheia · + − tamanho do texto · dentro do código, Ctrl+Enter executa.
Todo código desta aula pode ser editado e executado aqui mesmo. Altere os exemplos à vontade; o botão ↺ restaura o original. Suas alterações ficam salvas neste navegador; em Progresso você pode baixá-las em um arquivo e continuar em outro computador.
As visualizações mostram os algoritmos passo a passo: escolha a origem, use Próximo passo ou Animar. Nos exercícios, o botão ✓ Verificar testa seu código com várias entradas.
O que é um grafo?
Um grafo G = (V, E) é um conjunto de vértices (V, os objetos) ligados por arestas (E, as relações entre pares de objetos).
| Situação | Vértices | Arestas |
|---|---|---|
| rede social | pessoas | amizades ou “segue” |
| mapa | cidades, esquinas | estradas, ruas (com distância) |
| curso | disciplinas | pré-requisitos |
| internet | páginas, roteadores | links, cabos |
Listas e árvores são casos particulares de grafos. A diferença é que, num grafo geral, qualquer vértice pode se ligar a qualquer outro, inclusive formando ciclos.
Vocabulário dos grafos
| Termo | Significado |
|---|---|
| não dirigido | a aresta vale nos dois sentidos (amizade) |
| dirigido (dígrafo) | a aresta tem sentido u → v (“segue”, pré-requisito) |
| ponderado | cada aresta tem um peso (distância, custo, tempo) |
| grau | número de arestas de um vértice; no dirigido, grau de entrada e de saída |
| caminho | sequência de vértices ligados por arestas |
| ciclo | caminho que volta ao vértice inicial |
| conexo | existe caminho entre quaisquer dois vértices |
| árvore | grafo conexo e sem ciclos: tem exatamente V − 1 arestas |
Três formas de guardar o mesmo grafo
O desenho é só para nós. O programa precisa de uma estrutura: matriz de adjacência, lista de adjacência ou lista de arestas. Clique em um vértice e compare.
[(0, 1), (0, 3), (1, 2), (4, 1), …].Matriz de adjacência
Uma matriz V × V em que m[u][v] = 1 se existe a aresta u–v (ou o peso dela, num grafo ponderado), e 0 caso contrário.
- Existe a aresta u–v? Basta olhar
m[u][v]: O(1). - Quais os vizinhos de v? Percorre a linha toda: O(V), mesmo que v tenha 1 vizinho.
- Memória: V² posições, sempre.
Num grafo não dirigido a matriz é simétrica: m[u][v] == m[v][u].
Lista de adjacência
Para cada vértice, uma lista só com os seus vizinhos. Em Python, um dict de listas (ou uma lista de listas, se os vértices forem 0, 1, …, V−1).
- Vizinhos de v: direto, O(grau de v). É o que os percursos mais usam.
- Existe u–v? Procura na lista de u: O(grau de u).
- Memória: O(V + E).
Num grafo ponderado, cada vizinho vira um par (vizinho, peso).
[(u, v), …]) é o formato mais simples de ler da entrada e de ordenar por peso, mas responder “quem são os vizinhos de v” exige olhar todas as E arestas.Qual representação usar?
| Operação | Matriz | Lista de adjacência | Lista de arestas |
|---|---|---|---|
| memória | O(V²) | O(V + E) | O(E) |
| existe u–v? | O(1) | O(grau u) | O(E) |
| vizinhos de v | O(V) | O(grau v) | O(E) |
| percorrer o grafo todo | O(V²) | O(V + E) | — |
A maioria dos grafos reais é esparsa: cada vértice tem poucos vizinhos comparado com V. Uma rede com 1 milhão de pessoas e 100 amigos por pessoa teria uma matriz de 10¹² posições, quase todas zero.
Regra prática: lista de adjacência por padrão; matriz para grafos pequenos ou densos, ou quando a pergunta “existe u–v?” domina.
Lendo um grafo da entrada
Formato usado nos exercícios desta aula (e em maratonas de programação):
- primeira linha:
V E— número de vértices e de arestas; - depois,
Elinhasu v(ouu v peso); - os vértices são numerados de
0aV − 1.
Como os vértices são números, uma lista de listas basta: g[u] é a lista de vizinhos de u.
Busca em largura (BFS)
A BFS explora o grafo em ondas: primeiro a origem, depois todos os vizinhos dela, depois os vizinhos dos vizinhos… Uma fila garante essa ordem.
BFS em Python
- Coloque a origem na fila com distância 0.
- Enquanto a fila não estiver vazia, tire o primeiro vértice
v. - Para cada vizinho
wainda não descoberto:dist[w] = dist[v] + 1ewentra no fim da fila.
Cada vértice entra na fila uma vez e cada aresta é olhada uma vez (duas, se não dirigido): O(V + E).
collections.deque: popleft() é O(1). Com uma list, pop(0) desloca todos os elementos (O(n)) e a BFS fica quadrática.Teste de mesa da BFS
Acompanhe a fila e o dicionário dist. Repare que um vértice entra em dist no momento em que é descoberto, não quando sai da fila; por isso o 3 não entra duas vezes.
Busca em profundidade (DFS)
A DFS faz o contrário: segue um caminho o mais fundo possível e só volta (backtracking) quando não há vizinho novo. É o jeito de explorar um labirinto com a mão na parede.
DFS recursiva e com pilha
A versão recursiva é a mais curta: visitar v é marcar v e chamar a DFS para cada vizinho não visitado. A pilha de chamadas do Python guarda o caminho de volta.
Em grafos grandes, a recursão pode estourar o limite do Python (cerca de 1000 chamadas). Aí trocamos a pilha de chamadas por uma pilha explícita (list com append/pop).
| BFS | DFS | |
|---|---|---|
| estrutura | fila (FIFO) | pilha (LIFO) ou recursão |
| explora | por camadas de distância | um caminho até o fim |
| bom para | menor caminho sem pesos | componentes, ciclos, ordem topológica |
| custo | O(V + E) | O(V + E) |
Componentes conexas
Um grafo pode estar dividido em “ilhas” sem ligação entre si: as componentes conexas.
Para encontrá-las, percorra a partir de cada vértice ainda não visitado. Cada novo percurso (BFS ou DFS, tanto faz) descobre uma componente inteira.
Apesar do laço sobre todos os vértices, cada vértice e cada aresta são tratados uma vez só no total: O(V + E).
Detectando ciclos
Num grafo não dirigido, durante a DFS, encontrar um vizinho já visitado que não é o vértice de onde viemos significa que há dois caminhos até ele: existe um ciclo.
Por isso a pilha guarda pares (vértice, pai): a aresta de volta para o pai não conta como ciclo.
Uma árvore é justamente um grafo conexo sem ciclos. Ela tem sempre V − 1 arestas: uma a menos e ficaria desconexa, uma a mais e formaria um ciclo.
Qual estrutura, qual algoritmo?
Leia a situação para a turma, colete as respostas e só então clique.
Menor caminho sem pesos
Na BFS, um vértice é descoberto pela primeira vez pelo caminho com menos arestas. Guardando quem descobriu cada vértice (o pai), dá para reconstruir esse caminho.
Para montar a rota até t, siga os pais de t até a origem e inverta a lista.
Funciona quando todas as arestas “custam” o mesmo: número de estações, de conexões entre pessoas, de cliques entre páginas.
break), pois a distância dele já é definitiva.Labirintos: grafos implícitos
Nem sempre o grafo aparece como lista de arestas. Num mapa em grade, cada casa livre é um vértice e seus vizinhos são as casas livres acima, abaixo, à esquerda e à direita.
Não precisamos montar a lista de adjacência: calculamos os vizinhos na hora, somando os deslocamentos (±1, 0) e (0, ±1) e conferindo se a casa existe e não é parede.
A BFS a partir do início espalha as distâncias como uma mancha de tinta (flood fill). O mapa impresso mostra, em cada casa, quantos passos ela está do S.
#) no mapa e veja o número de passos até E mudar.Arestas com peso: algoritmo de Dijkstra
Com pesos diferentes, o caminho com menos arestas pode não ser o mais curto. O Dijkstra sempre fixa o vértice pendente mais próximo da origem e tenta melhorar (relaxar) as distâncias dos vizinhos dele.
Dijkstra em Python
dist[s] = 0e ∞ para os demais; a fila de prioridade começa com(0, s).- Tire o par de menor distância
(d, u). Sedfor maior quedist[u], é uma entrada antiga: ignore. - Para cada vizinho
vcom pesop: sedist[u] + p < dist[v], atualize e coloque(dist[v], v)na fila.
O heapq implementa a fila de prioridade (um heap, visto na aula de ordenação). Custo: O((V + E) log V).
Ordenação topológica
Em que ordem cursar disciplinas com pré-requisitos? Num grafo dirigido sem ciclos (DAG), uma ordem topológica coloca cada vértice depois de todos os que apontam para ele.
Algoritmo de Kahn em Python
- Calcule o grau de entrada de cada vértice.
- Coloque os de grau 0 em
prontos. - Retire um pronto, acrescente-o à ordem e diminua o grau de entrada de cada vizinho; quem chegar a 0 fica pronto.
- Se a ordem terminar com menos de V vértices, há um ciclo.
Pode haver várias ordens válidas. Usar um heapq em prontos escolhe sempre o menor número, deixando a resposta única. Custo: O(V + E), ou O((V + E) log V) com o heap.
Desafio: quantas ilhas?
No mapa, "1" é terra e "0" é água. Uma ilha é um grupo de terras ligadas na horizontal ou na vertical (diagonal não conta).
Conte quantas ilhas existem. Para o mapa ao lado, a resposta é 5.
É o problema das componentes conexas num grafo implícito em grade: para cada terra ainda não visitada, conte uma ilha nova e “pinte” toda a ilha com uma BFS ou DFS.
Mostrar uma solução
Qual será a saída?
Leia o código, escreva sua resposta e só então clique. Errou? Copie o código para o laboratório e execute.
Prática 1: representação e componentes
Nos exercícios, o grafo vem no formato V E seguido de E linhas u v, com vértices de 0 a V − 1. ✓ Verificar testa seu código com vários grafos, inclusive sem arestas.
1. Graus dos vértices básico
Leia um grafo não dirigido e mostre o grau de cada vértice (Grau de v: g, de 0 a V − 1). Por fim, mostre Maior grau: g (vértice v); em caso de empate, o vértice de menor número.
| Entrada | Saída esperada |
|---|---|
5 5 0 1 0 2 1 2 1 3 3 4 | Grau de 0: 2 Grau de 1: 3 Grau de 2: 2 Grau de 3: 2 Grau de 4: 1 Maior grau: 3 (vértice 1) |
Mostrar uma solução
2. Componentes conexas médio
Leia um grafo não dirigido e mostre quantas componentes conexas ele tem (Componentes: k) e quantos vértices tem a maior delas (Maior componente: m vértices). Vértices isolados formam componentes de 1 vértice.
| Entrada | Saída esperada |
|---|---|
6 3 0 1 1 2 3 4 | Componentes: 3 Maior componente: 3 vértices |
Mostrar uma solução
Prática 2: BFS e menor caminho
Os dois exercícios pedem o menor número de passos: é a BFS, num grafo lido da entrada e num grafo implícito em grade.
3. Distância entre dois vértices básico
Leia um grafo não dirigido e, na última linha, dois vértices s t. Mostre Distância: d, o menor número de arestas de s até t, ou Sem caminho.
| Entrada | Saída esperada |
|---|---|
6 6 0 1 0 2 1 3 2 3 3 4 4 5 0 5 | Distância: 4 |
Mostrar uma solução
4. Saída do labirinto médio
Leia L C e depois L linhas do mapa: . livre, # parede, S início e E saída. Andando nas 4 direções, mostre Passos: d (o menor número de passos de S até E) ou Sem saída.
| Entrada | Saída esperada |
|---|---|
3 4 S..E .##. .... | Passos: 3 |
Mostrar uma solução
Prática 3: grafos dirigidos e ponderados
Agora as arestas têm sentido: a linha u v significa apenas u → v.
5. Ordem das disciplinas médio
Cada linha u v diz que a disciplina u é pré-requisito de v. Mostre uma ordem para cursar todas (Ordem: a b c …), escolhendo sempre a de menor número entre as disponíveis. Se houver ciclo, mostre Impossível: há ciclo.
| Entrada | Saída esperada |
|---|---|
6 6 0 1 0 2 2 4 3 4 1 5 4 5 | Ordem: 0 1 2 3 4 5 |
Mostrar uma solução
6. Menores distâncias com pesos desafio
Leia um grafo dirigido com linhas u v peso (pesos não negativos) e, na última linha, a origem s. Para cada vértice, de 0 a V − 1, mostre Distância até v: d ou Distância até v: inalcançável. Use o Dijkstra com heapq.
| Entrada | Saída esperada |
|---|---|
3 2 0 1 5 1 2 1 0 | Distância até 0: 0 Distância até 1: 5 Distância até 2: 6 |
Mostrar uma solução
Laboratório livre
Espaço para testar ideias durante a aula. Sugestões: monte o grafo das suas disciplinas e gere uma ordem topológica; descubra se uma rede de amigos tem alguém a mais de 3 “apertos de mão” de você; adapte o labirinto para contar também os movimentos na diagonal.