busca em grafos como caminhar no grafo de modo a percorrer todos os seus vértices evitando repetições desnecessárias do mesmo vértice? e por onde começar? solução: necessidade de recursos adicionais que permitam reconhecer se o vértice foi ou não visitado aplicações em computação gráfica, compiladores, etc busca em grafos exemplos de algoritmos de busca da literatura pesquisa em extensão / largura / amplitude (breadth-first search - BFS) pesquisa em profundidade (depth-first search - DFS) em geral esses algoritmos assumem a estrutura de dados lista de adjacências LA para representar os seus grafos estruturas de dados para armazenar os vértices descobertos, mas ainda não completamente explorados: fila busca em largura pilha busca em profundidade 1
busca em largura / extensão / amplitude (breadth-first search - BFS) o grafo pode ser direcionado e não direcionado descobre as arestas de G que são alcançáveis a partir de um vértice origem s computa a distância (em número de arestas) de s para os vértices que são alcançáveis o algoritmo descobre todos os vértices a uma distância k do vértice origem antes de descobrir qualquer vértice a uma distância k+1 (o caminhamento é feito na largura), ou seja, o método visita todos os sucessores de um vértice antes de visitar os sucessores de seus sucessores busca em largura algoritmo estruturas de dados: grafo G(V,A) representado pela lista de adjacência LA vetor C[u], cor de cada vértice, u V vetor P[u], predecessor de cada vértice u V. Se u não tem predecessor ou ainda não foi descoberto P[u] é igual a 1 vetor D[u]: distância de cada vértice u V ao vértice s (origem) fila F: contém os vértices já descobertos na largura 2
busca em largura algoritmo cores dos vértices: branco: não visitados ainda cinza: vértice descoberto mas não teve a sua lista de adjacência totalmente examinada preto: vértice descoberto que já teve a sua lista de adjacência totalmente examinada se (u,v) A e o vértice u é preto, o vértice v tem que ser cinza ou preto vértices cinza podem ter alguns vértices adjacentes brancos e eles representam a fronteira entre vértices descobertos e não descobertos cada vértice é colocado e retirado da fila somente uma única vez funções ENQUEUE (enfileira) e DEQUEUE (tira da fila): operações sobre uma fila FIFO algoritmo de busca em largura: BFS(G) para cada vértice u V faça {para cada vértice pertencente ao grafo} C[u] branco; {inicialização de cada vértice para branco (não descoberto)} P[u] -1; {predecessor do vértice desconhecido} D[u] infinito; {distância como infinito} para cada vértice s V faça {para cada vertice origem} se (C[s] = branco) {escolha do vértice origem s} então C[s] cinza; {inicialização de s com cinza (considerado descoberto)} D[s] 0; {distância para o próprio vértice s é zero} ENQUEUE (F,s); {a fila F contém inicialmente apenas o vértice origem s (irá conter apenas {os vértices cinza, descobertos em largura)} enquanto (F <> ) faça {o loop é executado até que a fila esteja vazia, ou seja, não haja vértices cinza} u DEQUEUE (F); {vértice u contém o primeiro elemento da fila F, retira-se um vértice da fila} para cada vértice v LA[u] faça {para cada vértice pertencente à lista de adjacência de u que não foi} se (C[v] = branco) { descoberto ainda (igual a branco)} então C[v] cinza; {marca como descoberto (cinza)} D[v] D[u] + 1; {calcula a sua distância até s} P[v] u; {marca o seu predecessor como u} ENQUEUE (F,v); {e o coloca na fila F} C[u] preto; {quando todos os vértices adjacentes a u forem examinados, u passa a ser preto} {enquanto} {então} fim. {algoritmo} 3
algoritmo de busca em largura exemplo aplicação da busca em largura: caminho mais curto através da busca em largura, obtém-se o caminho mais curto (distância = número de arestas) de s V para os vértices que são alcançáveis em G o algoritmo BFS(G,s) constrói uma árvore de busca em largura que é armazenada na variável vetor P o procedimento abaixo imprime os vértices do caminho mais curso entre o vértice origem (s) e outro vértice qualquer do grafo: procedimento IMPRIME_CAMINHO (VO, V :inteiro); {VO = vértice origem} se (VO = V) então escreva (VO) senão se (P[V] = -1) então escreva ( Não existe caminho de,vo, até,v) senão IMPRIME_CAMINHO (VO, P[V]); escreva (V); 4
busca em profundidade (depth-first search - DFS) a estratégia é buscar o vértice mais profundo no grafo sempre que possível inicia em qualquer vértice ve e, em seguida, é buscado um vértice v adjacente a este as arestas são exploradas a partir do vértice v recentemente descoberto quando todas as arestas adjacentes a v tiverem sido exploradas, a pesquisa volta para explorar as outras arestas do vértice ve do qual v foi descoberto se ainda assim existir vértice não descoberto, então um novo vértice é selecionado e o processo começa novamente busca em profundidade algoritmo estruturas de dados: grafo G(V,A) representado pela lista de adjacência LA vetor C[u], cor de cada vértice, u V vetor P[u], predecessor de cada vértice u V. Se u não tem predecessor ou ainda não foi descoberto P[u] é igual a -1 vetor D[u]: marca quando o vértice é descoberto (de branco para cinza) vetor T[u]: marca quando o vértice é terminado (de cinza para preto) variável de tempo (TEMP): indica o instante em que o vértice é descoberto e terminado (carimbo de tempo) 5
busca em profundidade algoritmo cores dos vértices: no acompanhamento do progresso do algoritmo, cada vértice pode ser colorido de branco, cinza e preto branco: não visitados ainda. Nenhum vértice volta a ser branco cinza: vértice descoberto mas não teve a sua lista de adjacência totalmente examinada preto: vértice descoberto que já teve a sua lista de adjacência totalmente examinada e está terminado função visita_dfs: percorre recursivamente o grafo em profundidade algoritmo de busca em profundidade: DFS(G) TEMP 0; para cada i V[G] faça C[i] branco; P[i] -1; {inicialização do contador de tempo global} {para cada vértice i V[G]} {inicialização de cada vértice para branco (não descoberto)} {predecessor do vértice desconhecido} para cada i V[G] faça {para cada vértice i V[G]} se (C[i] = branco) {para cada vértice ainda não descoberto,..} então VISITA_DFS (i); {faz a pesquisa em profundidade} 6
busca em profundidade procedimento VISITA_DFS (i); C[i] cinza; {inicialização de cada vértice visitado de cinza} TEMP TEMP +1; D[i] TEMP; {atribui tempo da descoberta} para cada vértice v LA[i] faça {para cada vértice pertencente à lista de adjacência de i} se (C[v] = branco) então P[v] i; {atribui seu predecessor} VISITA_DFS (v); {é chamado uma única vez para cada vértice branco} C[i] preto; {teve sua lista de adjacências totalmente visitada e está terminado} TEMP TEMP +1; T[i] TEMP; {atribui tempo de término} algoritmo de busca em profundidade exemplo 7
classificação de arestas na busca em profundidade empregada para reunir informações importantes sobre o grafo aresta de árvore (u,v) quando v é descoberto pela primeira vez ao percorrer a aresta cor do vértice que é alcançado pela primeira vez a partir de u: branco no exemplo anterior, arestas (0,1), (1,2) são arestas de árvores aresta de retorno (u,v) conecta um vértice u com um antecessor v cor do vértice que é alcançado pela primeira vez a partir de u: cinza laços são considerados arestas de retorno no exemplo anterior, as arestas (2,0) e (2,2) são arestas de retorno aresta de avanço ou diretas (u,v) quando u é descoberto antes de v cor do vértice que é alcançado pela primeira vez a partir de u: preto liga um vértice visitado a um completamente explorado aresta de cruzamento (u,v) quando v é descoberto antes de u cor do vértice que é alcançado pela primeira vez a partir de u: preto no exemplo anterior, a aresta (3,1) é aresta de cruzamento arestas da busca em profundidade em um grafo não-orientado tem-se arestas da árvore arestas de retorno em um grafo orientado tem-se arestas da árvore arestas de avanço arestas de retorno arestas de cruzamento algumas aplicações da busca em profundidade pode ser usado para determinar se um grafo não orientado é conexo se for possível estabelecer um caminho de qualquer vértice para qualquer outro vértice de um grafo, diz-se que o grafo é conexo determinar se um grafo é acíclico ou se contém um ou mais ciclos um grafo acíclico é um grafo sem ciclos 8
outra opção de algoritmo de busca em profundidade o algoritmo pode ser reescrito utilizando uma pilha para eliminar a recursão logo que é encontrado o primeiro vértice w adjacente a v a) visita-se o nó w b) coloca-se o nó v em uma pilha c) faz-se v receber w no passo c), o processo de considerar nós adjacentes do (antigo) vértice v é interrompido, passando-se a procurar vértices adjacentes a w após visitar um vértice e tomá-lo como (novo) vértice v, se ele não tiver nós adjacentes ainda não visitados, toma-se como (novo) nó v aquele colocado no topo da pilha busca em largura x busca em profundidade existem problemas que são facilmente resolvidos usando busca em largura e outros usando busca em profundidade para determinar a existência de ciclos em grafos orientados mais conveniente usar busca em profundidade para determinar se um grafo é bipartido ou encontrar a menor distância de um vértice aos demais mais conveniente usar a busca em largura 9
Exercício 1) Considere o grafo G(V,A) com V = {1, 2, 3, 4, 5, 6} e A = {(1,2), (1,3), (2,6), (3,6), (5,2), (5,6), (4,6)}. Apresente o resultado da pesquisa em largura (ordem de descoberta dos vértices), considerando s=1. Exercício 2) Apresente o caminho mais curto (as arestas) entre o vértice origem (s = 1) e o vértice 4, utilizando o procedimento IMPRIME_CAMINHO no grafo do exercício anterior. Exercício 3) Apresente a operação de BFS sobre o grafo não orientado abaixo, assim como a distância (número de arestas) de cada vértice ao vértice s (vértice 1). Exercício 4) Apresente o caminho mais curto (as arestas) entre o vértice origem (s = 1) e o vértice 7, utilizando o procedimento IMPRIME_CAMINHO no grafo do exercício anterior. 10
Exercício 5) Apresente o andamento do algoritmo DFS de pesquisa sobre o grafo orientado abaixo assim como os tempos de descoberta e término (d/t) de cada vértice. Exercício 6) Que tipo de informação um programa (do tipo DFS) pode devolver para comprovar que o grafo não tem ciclo (é acíclico)? Exercício 7) Reescreva o algoritmo DFS, utilizando uma pilha para eliminar a recursão (veja Veloso P. Estruturas de Dados). 11