Busca em Profundidade
“O Senhor olha dos céus para os filhos dos homens,
para ver se há alguém que tenha entendimento, alguém que busque a Deus.”
Busca em Grafos (Revisão)
• Buscas (ou buscas) em grafos são estratégias algorítmicas usadas para percorrer um grafo
– Seguem/respeitam as arestas
• Duas buscas mais simples
– Busca em extensão (já vimos) – Busca em profundidade (hoje!)
Motivações para a Busca em
Pronfundidade
• Vamos citar duas aplicações potencias da DFS
• Problema 1: Dado um grafo de amizades, encontrar comunidades/grupos de pessoas interligadas (direta ou indiretamente)
• Em grafos não-direcionados: achar componentes conectados do grafo
Motivações para a DFS
• Problema 2: Ao criar o currículo de um
curso, com várias disciplinas que podem estar inter-relacionadas por restrições de
“pré-requisito”, é importante verificar se não há uma dependência cíclica. Exemplo:
– MD2 é pré-requisito de Algoritmos
– Algoritmos é pré-requisito de Programação 2 – Programação 2 é pré-requisito de MD2
Busca em Profundidade
• Tenta seguir sempre o mais “fundo” possível • Ao chegar em um vértice u, escolhe um
vizinho branco qualquer e já inicia uma visita a ele
Busca em Profundidade
• Cada vértice pode estar em três estados
– Branco: não-visitado – Cinza: visita iniciada – Preto: visita terminada
• O vértice se torna cinza logo que a busca o atinge, e preto quando todos os seus
Busca em Profundidade
• Pseudocódigo abstrato da visita
DFS-VISIT (vértice u)
atribui cinza a u //inicio da visita a u
para cada v branco de Adj[u] DFS-VISIT(v) //visita v
Busca em Profundidade
• Melhorando o pseudocódigo
– Atribuir o instante do início da visita (quando o vértice se torna cinza)
– Atribuir o instante do fim da visita (quando o vértice se torna preto)
– Guardar a árvore de busca (mostra os caminhos seguidos pela busca partindo do vértice de início)
Busca em Profundidade
• Vamos usar as seguintes variáveis
– cor[u] – conforme explicado anteriormente – i[u] – momento em que a visita a u é iniciada – f[u] – momento em quea visita a u é finalizada
[u] ou ante[u] – vértice antecessor de u na busca
• Para setar i[] e f[], vamos usar uma variável global tempo
Pseudocódigo – Visita
• Com as variáveis anteriores
DFS-VISIT(grafo G, vértice u)
cor[u] = CINZA; i[u] = tempo ++;
para cada v de Adj[u] if cor[v] = BRANCO ante[v] = u;
DFS-VISIT(G,v); cor[u] = PRETO;
Busca em Profundidade
• Quem faz a primeira chamada? Quando as variáveis são inicializadas?
• Vamos definir um procedimento DFS-START para o início da busca, em duas versões:
1. início único (em um vértice s)
Pseudocódigo – Início Único
• Faz apenas um início da busca, em um dado vértice s
DFS-START(grafo G, vértice s)
para todo vértice v cor[v] = BRANCO i[v] = f[v] = -1 ante[v] = -1
tempo = 1
Pseudocódigo – Com Reinícios
• Faz novos inícios da busca, enquanto houver vértice branco (não-visitado)
DFS-START(grafo G)
para todo vértice v cor[v] = BRANCO i[v] = f[v] = -1 ante[v] = -1
tempo = 1
Exemplo
1 4 2 5 3 6 1/12 4/5 2/11 3/10 6/9 7/8• Uma possível execução da busca, mostrando os tempos i/f de cada vértice
Saídas
• Por enquanto, as principais saídas do algoritmo são os arrays:
– ante[] – i[] e f[]
• Veremos algumas propriedades associadas a essas arrays, após a execução de uma
2.1. Sobre o Array ante[]
• Como na busca em extensão, a busca em profundidade
com início único gera uma árvore
– Cada posição ante[v] guarda o pai (antecessor) de um vértice v
• No exemplo dado antes, a árvore resultante seria esta:
1
2
4
5
Árvore de Busca
• No caso de início único, esta árvore
representa caminhos quaisquer do vértice de origem s a cada um dos outros vértices • Mas não há garantia de que são mínimos! • Para imprimir um caminho específico, você
pode usar o mesmo algoritmo mostrado para a busca em extensão
Floresta de Busca
• Se for usada a busca em profundidade
com reinícios, cada reinício gera uma árvore
– Ou seja, a saída é uma floresta (conjunto de árvores)
• Os caminhos representados em cada árvore são pouco úteis
Voltando ao Problema 1
• Então, como fazer para achar os grupos a partir de um grafo das relações de amizade (de uma rede social, por exemplo)?
2.2. Sobre os Arrays i[] e f[]
• Definem, para cada nó u, o intervalo de tempo de
busca dele:
[ i[u] ; f[u] ]
• Se w é descendente (direto ou indireto) de u na busca, então o intervalo de w está contido no intervalo de u
– Ou seja: i[u] < i[w] < f[w] < f[u]
– O descendente começa a busca depois e termina antes!
tempo intervalo de u intervalo de w tempo intervalo de u intervalo de w
Quer que eu desenhe?
• Se w é descendente de u :
• Se não existe relação de
descendência (são de ramos distintos da árvore de busca):
Estrutura de Parênteses
• A propriedade anterior é chamada estrutura de parênteses, porque uma consequência dela é que a DFS gera parênteses
balanceados, ao fazer assim:
– No início da visita, imprimir “(u” – No final da visita, imprimir “u)”
• Exemplo
– Ordem de visita:
2.3. Classificação das Arestas
• A busca em profundidade em digrafos induz uma classificação das arestas assim:
– De árvore: formam a árvore de busca (arestas não tracejadas na figura)
– De retorno: leva de volta a um ancestral na árvore de busca
– De adiantamento: “saltam” para um descendente da árvore
– De cruzamento: nós sem relação de descendência
Voltando ao Problema 2: Detectar
Ciclo
Um digrafo G apresenta algum ciclo
se e somente se
uma busca em profundidade em G produz uma aresta de retorno
Motivação
• Problema 3: Dado um currículo com várias disciplinas que podem estar
inter-relacionadas por restrições de “pré-requisito”. Definir uma sequência para completar o
currículo cursando uma disciplina por semestre.
Representado com Grafos
• Um grafo de dependências (binárias) entre atividades • Um arco (x,y) indica que a
atividade x tem que ser realizada antes de y
• Ordem para realizar uma-a-uma essas atividades todas:
Ordem Topológica
• Uma ordem topológica é uma ordenação linear dos vértices, tal que
– Para todo arco (x,y) do grafo, o vértice x deve estar posicionado antes do vértice y
• Informalmente:
– “Uma sequência em que todo arco vai da esquerda para a direita”
Ordem Topológica
• Só existe em grafos acíclicos direcionados (DAGs, em inglês)
– Se tiver ciclo, não é possível satisfazer as dependências!
• É comum haver várias ordens topológicas válidas em um mesmo DAG
• Exemplos de ordens topológicas no grafo anterior:
– 7,5,3,11,8,2,10,9 – 7,5,11,2,3,10,8,9
Ordenação Topológica
• O problema de achar uma ordem topológica é chamado de ordenação topológica
• Uma pesquisa em profundidade pode calcular uma ordem topológica em um DAG
• Veremos as mudanças nos procedimentos
Ordenação Topológica
• Mudanças em DFS-VISIT-OT
– Quase igual ao DFS-VISIT padrão – Acessa uma pilha de vértice global
– No final da visita, adiciona o vértice atual na pilha
• Procedimento principal: ORDENA-TOPOLOG
– Quase igual ao DFS-START com reinícios – Inicializa a pilha e a retorna ao final
Pseudocódigo
• Procedimento principal
ORDENA-TOPOLOG(grafo G)
pilha = []
< igual ao DFS-START com reinícios>
Pseudocódigo
• Visita
DFS-VISIT-OT(grafo G, vértice u) < igual ao DFS-VISIT >
Exemplo
Ordenação Topológica
• Observe que a pilha final contém os vértices em ordem decrescente de f[v]
– O último (fundo) da pilha foi o primeiro finalizado – O primeiro (topo) da pilha foi o último finalizado
• Porém, rodar DFS-START primeiro e ordenar por f[v] posteriormente seria bem menos eficiente
Esboço da Prova de Corretude
• Resultados básicos– Um DAG não tem aresta de retorno
• Porque não tem ciclo
– Para toda aresta (x,y), a busca encerra y antes de x
• Na busca, esta aresta é analisada a partir de x • Se y estiver branco, y se tornará filho de x • Se y estiver preto, é porque y já foi encerrado
• (Não pode estar cinza, pois seria aresta de retorno)
– Assim, para toda aresta (x,y), o y é inserido primeiro na pilha
Complexidade
• Similar à busca em extensão...
– Inicializa atributos de cada vértice – tempo O(V) – Analisa todas as arestas de saída de cada vértice –
tempo total O(E)
• Assim, a complexidade será O(V+E) ou simplesmente:
Observações Finais
• A busca em profundidade é uma estratégia algorítmica para percorrer um grafo
• Vimos como, com pequenas alterações,
podemos resolver pelo menos três problemas:
– Achar componentes conectados – Detectar ciclos
– Ordenação topológica
• Veremos outro algoritmo baseado nela na próxima aula...