edubun
Português

Busca em grafos: BFS e DFS

Em 1736, Leonhard Euler mostrou que nenhum passeio atravessa cada uma das sete pontes de Königsberg exatamente uma vez, e a teoria dos grafos começou com essa resposta.

Pontos e linhas

Um grafo é um conjunto de pontos, chamados vértices ou nós, ligados por linhas, chamadas arestas. A ideia é simples e serve para uma variedade enorme de coisas: cidades ligadas por estradas, páginas web ligadas por links, pessoas ligadas por amizades, quadrados de um labirinto ligados onde não há parede. Depois que um problema é desenhado como grafo, o mesmo punhado de algoritmos responde a perguntas como "dá para ir daqui até lá?" e "qual é o caminho mais curto?"

ContextoA cidade de Königsberg (hoje Kaliningrado) tinha sete pontes ligando duas ilhas e as duas margens do seu rio. As pessoas se perguntavam se um passeio poderia atravessar cada ponte exatamente uma vez. Em 1736, Leonhard Euler respondeu que não. O passo decisivo dele foi jogar o mapa fora: só importavam as áreas de terra e as pontes entre elas. Cada área de terra virou um ponto e cada ponte uma linha. Depois ele mostrou que um passeio assim exige que todo ponto, exceto talvez o início e o fim, tenha um número par de linhas, e em Königsberg as quatro áreas de terra tinham um número ímpar.
A B C D E F
O grafo de exemplo desta unidade. A é o início; F é o objetivo. Arestas: A‑B, A‑C, B‑D, C‑D, C‑E, D‑F, E‑F.

Guardando um grafo no código

Um jeito comum de guardar um grafo é a lista de adjacência: para cada vértice, uma lista dos seus vizinhos. As notas do curso 6.006 do MIT usam exatamente isso, um array ou dicionário Adj em que Adj[u] lista os vértices ligados a u. Para o grafo acima, fica assim:

const graph = {   // lista de adjacência
  A: ["B", "C"],
  B: ["A", "D"],
  C: ["A", "D", "E"],
  D: ["B", "C", "F"],
  E: ["C", "F"],
  F: ["D", "E"],
};

Busca em largura: anel por anel

A busca em largura (BFS) explora um grafo em anéis em volta do início. Primeiro o próprio início, à distância 0. Depois todos os vizinhos dele, à distância 1. Depois todo vértice novo ao lado desses, à distância 2, e assim por diante. Para isso ela mantém uma fila, em que o primeiro a entrar é o primeiro a sair. Pegue o vértice da frente da fila, visite-o e acrescente os vizinhos ainda não vistos no fim. A partir de A, no exemplo, a BFS visita A, depois B e C, depois D e E, depois F. Como ela avança para fora um anel por vez, a primeira vez que a BFS chega a um vértice ela encontrou um caminho com o menor número de arestas. F está a 3 arestas de A, por exemplo A, B, D, F.

function bfs(graph, start) {
  const seen = new Set([start]);
  const queue = [start];
  const order = [];
  while (queue.length) {
    const u = queue.shift();        // primeiro a entrar, primeiro a sair
    order.push(u);
    for (const v of graph[u]) {
      if (!seen.has(v)) { seen.add(v); queue.push(v); }
    }
  }
  return order;   // A B C D E F
}

Busca em profundidade: até onde der, depois volte

A busca em profundidade (DFS) se comporta como alguém num labirinto que mantém uma mão na parede. Ela segue um caminho o mais fundo possível. Quando chega a um beco sem saída ou a um lugar já visto, ela volta até o último ponto de escolha e tenta a próxima opção. Ela usa uma pilha em vez de uma fila, último a entrar, primeiro a sair, e é muito natural de escrever com recursão, em que a pilha de chamadas faz o trabalho de lembrar. A partir de A, uma DFS recursiva visita A, B, D, C, E, F. A DFS chega a todo vértice a que a BFS chega, mas o caminho que ela segue não é necessariamente o mais curto.

function dfs(graph, u, seen = new Set(), order = []) {
  seen.add(u);
  order.push(u);
  for (const v of graph[u]) if (!seen.has(v)) dfs(graph, v, seen, order);
  return order;   // ordem: A B D C E F
}
Carregando

Arraste para girar. Role ou faça pinça para dar zoom.

Um labirinto como grafo: cada quadrado livre é um vértice, ligado aos quadrados livres ao lado dele. Rode a BFS e depois a DFS no mesmo labirinto e compare os quadrados visitados e o comprimento do caminho encontrado.

Qual usar

No primeiro labirinto do laboratório, a BFS visita 105 quadrados e acha um caminho de 22 movimentos, o mais curto possível. A DFS por acaso mergulha na direção do objetivo e visita só 31 quadrados, mas o caminho dela leva 28 movimentos. Nenhuma é sempre mais barata. As duas tratam cada vértice e cada aresta um número fixo de vezes, então as duas rodam em tempo O(V + E) para V vértices e E arestas. Escolha a BFS quando precisar do menor número de passos: o menor número de movimentos num quebra-cabeça, o menor número de ligações entre duas pessoas. Escolha a DFS quando precisar explorar tudo ou seguir escolhas até o fim: conferir se todas as salas de um labirinto se ligam, ou testar as jogadas de um quebra-cabeça uma linha de cada vez e voltar quando travar. Quando as arestas têm comprimentos diferentes, como as estradas, nenhuma das duas basta sozinha; métodos como o algoritmo de Dijkstra estendem a ideia da BFS com distâncias.

As duas buscas precisam lembrar quais vértices já viram. Sem isso, um grafo com um ciclo (de A para B para D para C e de volta para A) faria a busca dar voltas para sempre.
grafoVértices ligados por arestas
vértice (nó)Um ponto de um grafo
arestaUma linha que liga dois vértices
lista de adjacênciaPara cada vértice, a lista dos seus vizinhos
filaPrimeiro a entrar, primeiro a sair; usada pela BFS
BFSBusca em largura: anel por anel; acha caminhos com menos arestas
DFSBusca em profundidade: fundo primeiro, depois volta; usa uma pilha
O(V + E)O tempo de execução da BFS e da DFS
  1. No grafo de exemplo, quantas arestas tem o caminho mais curto de A até F?
  2. Quantas arestas tem o grafo de exemplo?
  3. Qual estrutura de dados a busca em largura usa?
    • Uma fila (primeiro a entrar, primeiro a sair)
    • Uma pilha (último a entrar, primeiro a sair)
    • Uma lista ordenada
    • Nenhuma estrutura
  4. Qual busca garante achar um caminho com o menor número de arestas?
    • BFS
    • DFS
    • As duas, sempre
    • Nenhuma
  5. Ponha os vértices na ordem em que a BFS a partir de A os visita (vizinhos em ordem alfabética).
  6. Ponha os vértices na ordem em que a DFS recursiva a partir de A os visita (vizinhos em ordem alfabética).
  7. Na grade de 15 por 9 do laboratório, sem nenhuma parede, quantos movimentos tem o caminho mais curto de S (canto superior esquerdo) até G (canto inferior direito)?
  8. Por que a BFS e a DFS precisam lembrar quais vértices já viram?
    • Senão um ciclo no grafo faria a busca dar voltas para sempre
    • Para mostrá-los no fim
    • Para ordená-los
    • Só é necessário na DFS
  9. Em que ano Euler resolveu o problema das pontes de Königsberg?
    • 1683
    • 1736
    • 1843
    • 1945
  10. Qual é o tempo de execução da BFS num grafo com V vértices e E arestas?
    • O(V + E)
    • O(V × E)
    • O(log V)
    • O(2ⱽ)
  11. Você quer saber o menor número de ligações do tipo amigo de amigo entre duas pessoas numa rede social. Qual busca?
    • BFS
    • DFS
    • Busca binária
    • Merge sort
Pontos-chave
  • Um grafo é um conjunto de vértices ligados por arestas; listas de adjacência o guardam.
  • A BFS usa uma fila, vai anel por anel e acha caminhos com menos arestas.
  • A DFS usa uma pilha (ou recursão), vai fundo primeiro e depois volta.
  • As duas rodam em O(V + E) e precisam lembrar os vértices vistos.
  • A solução de Euler de 1736 para as pontes de Königsberg deu início à teoria dos grafos.
Fontes deste curso (11)
  • MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
  • lecture notes 1 Introduction, 3 Sorting, 9 Breadth-First Search, 10 Depth-First Search (ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes) and Fall 2011 lecture 13 BFS notes (ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011)
  • Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (MIT Press)
  • with the CLRS-based Khan Academy algorithms unit by Thomas Cormen and Devin Balkcom of Dartmouth (khanacademy.org/computing/computer-science/algorithms: binary search, selection sort analysis, merge sort, quicksort overview and analysis)
  • MacTutor History of Mathematics, Al-Khwarizmi (mathshistory.st-andrews.ac.uk/Biographies/Al-Khwarizmi: word algorithm from the Latin title Algoritmi de numero Indorum)
  • D. E. Joyce, Euclid's Elements Book VII Propositions 1 and 2 (mathcs.clarku.edu/~djoyce/elements/bookVII)
  • Wolfram MathWorld, Merge Sort (mathworld.wolfram.com/MergeSort.html: proposed by John von Neumann in 1945, after Knuth 1998 p. 159)
  • Stanford CS "Tony Hoare: Quicksort" (cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/quicksort.html: devised while a student at Moscow State University for Russian-English machine translation with a tape dictionary, programmed in Algol 60 and published 1961; the year 1959 from the Wikipedia Quicksort article and its cited sources)
  • Bodleian Libraries, Ada Lovelace (blogs.bodleian.ox.ac.uk/adalovelace: 1843 paper, Note G Bernoulli numbers)
  • Fourmilab text of Menabrea's Sketch with Lovelace's notes (fourmilab.ch/babbage/sketch.html)
  • Seven Bridges of Konigsberg (Euler 1736) and Tower of Hanoi (Edouard Lucas, 1883) checked against the Wikipedia articles and their cited sources.

Data de verificação: 2026-10-17