6 - BUSCA EM GRAFOS
Última revisão: 9/6/2001
Conteúdo
Busca em profundidade
Busca de articulação
Ordenação topológica
Identificação de componentes fortemente conexos
Busca em largura
Exercícios
Vários problemas representados por um grafo podem ser
resolvidos efetuando uma busca nesse grafo. Às vezes é
preciso visitar todos os vértices de um grafos, as vezes o
problema pode ser resolvido visitando somente um subconjunto dos
vértices. Consideremos por exemplo o problema do caminho mais
curto. Os algoritmos apresentados para resolver esse problema fazem um
percurso exaustivo de todos os vértices. Não precisa ser
assim se, por exemplo, queremos o caminho mais curto até um
vértice em particular. Nesse caso, assim que ele se encontra no
conjunto dos vértices já visitado, não é
preciso continuar o algoritmo.
Basicamente, existem duas técnica de busca em grafos: a
busca em profundidade (depth-first search) e a busca em largura
(breadth-first search).
Vamos ver agora o algoritmo e algumas aplicações.
Seja um grafo G = (V,E) que contém n
vértices. Seja tambem uma representação que
indica, para cada vértice, se ele foi visitado ou
não. Eis uma versão recursiva do algoritmo de busca em
profundidade que visita todos os vértices:
procedimento Busca(G: Grafo)
Para Cada vértice v de G:
Marque v como não visitado
Para Cada vértice v de G:
Se v não foi visitado:
Busca-Prof(v)
procedimento Busca-Prof(v: vértice)
Marque v como visitado
Para Cada vértice w adjacente a v:
Se w não foi visitado:
Busca-Prof(w)
Note que esse algoritmo funciona com um grafo desconexo. Se
já sabemos que o grafo é conexo, podemos chamar
diretamente a função Busca-Prof, escolhendo
arbitrariamento um vértice inicial.
Para implementar esse algoritmo, é a estrutura de
adjacência que aparece como candidata ideal. Isso porque a
principal operação efetuada pelo algoritmo é a
escolha de um vértice adjacente e já sabemos que nesse
caso a estrutura de adjacência é a melhor
opção. Supondo então que a estrutura de
adjacência é utilizada para implementar a busca, podemos
ver que o tempo de execução do algoritmo é em
O(max(a,n)), onde a e n representam o
número de arestas e de vértices, respectivamente. Como
cada vértice deve ser visitado, o algoritmo necessariamente
fará n chamadas do procedimento Busca-Prof,
muitas delas recursivas. Em cada chamada, todos os vértices
adjacentes serão testados. No total, o número de testes
realizados será igual ao número de arestas. Então
o tempo total gasto para as chamadas a Busca-Prof é em
O(a). A esse tempo devemos acrescentar o tempo gasto no procedimento
Busca: o tempo de inicialização, e o tempos para
testar, para cada vértice, se ele foi marcado. No total isso
dá um tempo em O(2n) = O(n). Portanto, a busca em profundidade
tem um tempo de execução em O(a+n). Na verdade temos um
tempos em O(max(a,n)): se o grafo tem mais arestas que vértices
temos um tempo em O(a). No caso contrário, temos um tempos em
O(n).
Considere agora o grafo ilustrado na figura 1a e a estrutura de
adjacência ilustrada na figuras 1b que representa esse grafo.

Figura 1
Eis o traço de execuão do algoritmo com esse exemplo:
Busca-Prof(1)
Busca-Prof(2)
Busca-Prof(4)
Busca-Prof(5)
Busca-Prof(3)
Busca-Prof(6)
Busca-Prof(7)
Busca-Prof(8)
Quando o algoritmo chega ao vértice 5, não há
nenhum vizinho dele que não foi visitado. Ele retorna dessa
chamada para continuar a busca a partir do vértice anterior que
é o vértice 4. A busca continua com o próximo
vizinho de 4 que não foi visitado: o vértice 3. Como o
vértice 3 não tem vizinhos que não foram visitados,
o algoritmo retorna imediatamente ao vértice 4, para continuar
a busca com o vértice 6, e assim por diante chegamos ao
último vértice visitado que é vértice 8.
Note que à busca em profundidade corresponde uma
árvore geradora. No exemplo da figura 1, obtemos a
árvore geradoa ilustrada na figura 2:

Figura 2
Vamos ver agora aplicações da busca em profundidade.
Um vértice em um grafo não direcionado simples e
conexo é uma articulação se sua
remoção torna o grafo resultante desconexo. A
identificaço de articulação pode ser importante
em redes de computadores para identificar os pontos frágeis de
uma rede. O algoritmo de busca em profundidade pode ser usada para
identificar uma articulação. No grafo da figura 1a, o
vértice 4 é uma articulação. Um grafo
é dito biconexo se ele não contém nenhuma
articulação.
Vamos agora explicar essencialmente os princípios do
algoritmo para procurar as articulações de um grafo.
Primeiro, supomos que foi realizada uma uma busca em profundidade
para obter uma árvore geradora. Essa árvore obtida
será examinada para determinar a existência de
articulação. Vamos ver o que podemos deduzir a partir de
cada nodo n dessa árvore:
- n é a raiz: Se n tem somente um
filho, isso significa que qualquer vértice pode ser
alcançado a partir desse filho. Então, tirando n
do grafo não tornaria ele desconexo. Portanto, nesse caso, esse
vértice não é uma articulação. Se
n tem mais de um filho, isso significa que não podemos
alcançar todos os vértices a partir de qualquer um dos
filhos (senão o algoritmo não teria volta à raiz
para recomeçar a busca com outros vizinhos). Nesse caso,
obrigatoriamente devemos passar por n para ir de qualquer nodo
de uma subárvore até um nodo de uma outra
subárvore. Portanto, retirar n deixaria o grafo
desconexo.
- n é uma folha: Todo vértice que
é uma folha da árvore não é uma
articulação. Isso porque já sabemos que a
remoção de um vértice pendente em uma
árvore não a deixa desconexo.
- n é um nodo interno: Retirando esse nodo, o
grafo fica conexo somente se existe, para cada um das
subárvores de n, uma ligação entre ele e
um nodo ancestral de n.
Agora que entendemos o princípio do algoritmo, podemos
explicá-lo em detalhes. Primeiro, é preciso modificar o
algoritmo de busca em profundidade para que ele retorne um vetor
prenum[1..n] que indica, para cada vértice do grafo, a
sua ordem no percurso. Eis a versão que precisamos, supondo que
prenum é uma variável global.
procedimento Busca(G: Grafo)
pnum := 0
Para Cada vértice v de G:
Marque v como não visitado
Para Cada vértice v de G:
Se v não foi visitado:
Busca-Prof(v)
procedimento Busca-Prof(v: vértice)
Marque v como visitado
pnum := pnum + 1
prenum[v] := pnum
Para Cada vértice w adjacente a v:
Se w não foi visitado:
Busca-Prof(w)
Eis as etapas do algoritmo para identificação de
articulação:
- Realizar uma busca em profundidade, começando por qualquer
vértice, onde para cada vértice atribuimos um
número indicando sua posição na sequência
de visitas. Seja T a árvore obtida e prenum[1..n] o
vetor que indica para cada vértice a sua ordem na busca.
- Agora, para cada nodo, vamos identificar um valor que corresponde
à ordem do primeiro vértice visitado com o qual ele
está ligado (seja diretamente por uma aresta ou seguindo um
caminho na subárvore que ele domina). Para identificar esses
valores, realizamos um percurso em pós-ordem da árvore
T. Para cada vértice v, calculamos menor[v], que
representa o mínimo entre:
- prenum[v]
- prenum[w] para cada w tal que existe uma aresta
(v,w) em G que não aparece em T.
- menor[x] para cada filho x de v em T.
- Agora, as articulações podem ser facilemente
identificadas da seguinte maneira:
- A raíz de T é uma articulação se e
somente se ela tem mais de um filho.
- Um vértice v que não é a raíz
é uma articulação se e somente se v tem
um filho x tal que menor[x]
prenum[v]
A figura 3 ilustra a aplicação desse algoritmo com o
grafo da figura 1a. Está representada a árvore geradora
produzida pela busca em profundidade com, escrito em azul do lado
esquerda, o ordem de visita de cada vértices. Em linha quebrada
são representadas as arestas que ficam fora da
árvore. Em vermelho do lado direita são indicados os
valores menor[v] para cada vértice v. Podemos
conferir que somente dois nodos têm um filho cujo valor
menor[v] é maior ou igual à sua ordem na busca:
os vértices 4 e 6 que, como podemos verificar, são as
únicas articulações do grafo.

Figura 3
Vamos ver agora como o algoritmo de busca em profundidade pode ser
utilizado para resolver problemas representados com grafos
direcionados. Nesses casos, o algoritmo é o mesmo. Só temos que
tomar cuidado com a seleção dos vértices
adjacente. Num grafo direcionado, um vértice w e adjacente a um
vértice v se existe uma aresta que sai de v e
chega em w. Duas aplicações são
apresentadas nas próximas seções.