Aula 10 - Busca em Grafos (BFS e DFS)
Universidade Federal do Rio Grande do Sul
Instituto de Informática
Departamento de Informática Teórica
Estes slides utilizam conteúdo adaptado da bibliografia da disciplina e também de notas de aula e slides prévios dos professores Bruno Grisci, Rodrigo Machado, André Grahl Pereira, Lucas Nunes Alegre, Marcus Ritt e Luciana Buriol.
Problemas de busca em grafos:
Conectividade: Dado um Grafo G e um nó v, descobrir todos os nós alcançáveis a partir de v.
Caminho Mais Curto: Dado um Grafo G e um nó v, descobrir o caminho mais curto a partir de v para todos os outros nós.
Existem duas estratégias principais para algoritmos de busca em grafos:
G1 =
Pergunta: em que ordem os vértices serão alcançados em cada uma das estratégias?
Pergunta: como podemos avaliar o custo desses procedimentos?
Um grafo simples G=(V,E) possui dois parâmetros naturais:
Esses parâmetros (n e m) fazem parte da análise, e dependendo pode não ser muito claro como se relacionam.
Por exemplo, qual seria o melhor custo assintótico: O(m^2) ou O(n^2)?
Observe que há algumas relações já vistas entre n e m:
Matriz de adjacência
\begin{array}{l|ccccccc} & A & B & C & D & E & F & G\\ \hline A & 0 & 1 & 0 & 0 & 1 & 0 & 0\\ B & 0 & 0 & 0 & 0 & 1 & 1 & 0\\ C & 0 & 0 & 0 & 1 & 0 & 0 & 0\\ D & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ E & 0 & 0 & 1 & 0 & 0 & 1 & 0\\ F & 0 & 0 & 0 & 1 & 0 & 0 & 1\\ G & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ \end{array}
Lista de adjacência (Python)
Como m \leq n^2, o custo O(m+n) nunca é pior que O(n^2), sendo muito melhor para grafos esparsos.
Listas são mais eficientes para implementar buscas pelo grafo.
Seja n = |V| e m = |E|.
| Operação | Matriz | Lista |
|---|---|---|
| Espaço | O(n^2) | O(n + m) |
| Verificar aresta (u,v) | O(1) | O(\deg(u)) |
| Listar vizinhos de v | O(n) | O(\deg(v)) |
| Inserir aresta | O(1) | O(1) |
| Remover aresta | O(1) | O(\deg(u)) |
Algoritmo Genérico de Busca
Afirmação: Ao fim do Algoritmo Genérico, um vértice v é marcado como explorado, se e somente se, existe um caminho entre s e v.
Prova (\Rightarrow): Assuma que o vértice v é marcado explorado. Faremos indução no número de iterações.
Afirmação: Ao fim do Algoritmo Genérico, um vértice v é marcado como explorado, se e somente se, existe um caminho entre s e v.
Prova (\Rightarrow): Assuma que existe um caminho entre s e v.
Visualização: BFS vs. DFS - https://www.youtube.com/shorts/1-elk8F8_UM
Busca em profundidade (DFS) pode ser implementada de forma iterativa:
Busca em profundidade (DFS) pode ser implementada de forma bem simples por meio de recursão:
Breadth-First Search (BFS): Explora a partir de s todos os vértices adicionando vértices em camadas.
BFS (Camadas):
No grafo de exemplo, temos os seguintes níveis (a partir de ‘A’):
BFS também pode ser implementado de uma forma bastante simples utilizando uma fila.
O código é essencialmente idêntico à versão iterativa de DFS, só mudando a estrutura de dados:
dict| Operação | Custo |
|---|---|
k in d |
O(1) em média, O(n) no pior caso |
d[k] |
O(1) em média, O(n) no pior caso |
d[k] = v |
O(1) em média, O(n) no pior caso |
del d[k] |
O(1) em média, O(n) no pior caso |
| Iteração / cópia | O(n) |
list| Operação | Custo |
|---|---|
l[i], l[i] = x, len(l) |
O(1) |
append(x), pop() no fim |
O(1) amortizado |
x in l, iteração |
O(n) |
insert(i, x), pop(i), del l[i] |
O(n) |
l[i:j] |
O(k) |
collections.deque| Operação | Custo |
|---|---|
append(x), appendleft(x) |
O(1) |
pop(), popleft() |
O(1) |
len(d) |
O(1) |
extend(t), extendleft(t), rotate(k) |
O(k) |
remove(x) |
O(n) |
Árvore de Busca. Dada uma busca (DFS ou BFS) em G a partir de um nó v, construir uma árvore associada a essa busca onde cada nó aponta para o elemento que o alcançou diretamente.
parent, preenchido ao inserirmos um nó na estrutura.Veja a modificação do algoritmo DFS iterativo para construir e retornar a árvore:
Propriedade dos Níveis: Seja T a árvore resultante da BFS para o grafo G = (V, E) e (x, y) uma aresta qualquer de G:
- Então, os níveis de x e y na árvore de busca diferem no máximo por 1.
Isto ocorre porque, se a diferença fosse maior (ex: y dois ou mais níveis abaixo de x), no momento em que a BFS visitou os vizinhos de x, o vértice y teria sido colocado na fila logo no nível abaixo de x, limitando a diferença de nível a no máximo 1.
Afirmação: O algoritmo BFS tem tempo de execução O(n + m).
Detalhes da Prova:
Detalhamento:
Considere um grafo com quantidade infinita de nós (mas nenhum nó com grau infinito).
P. Qual a diferença de comportamento da DFS e BFS nesse caso?
Ver Github da disciplina: https://github.com/BrunoGrisci/projeto-e-analise-de-algoritmos/blob/main/paa1/graph_search.ipynb
?