Aula número 1

Dia 2-10-2000

Patrícia Isabel Assis Valadas da Silva

 

 

 

Nesta aula vimos duas maneiras possíveis de se percorrer grafos: em profundidade (DFS) e em largura (BFS).

Nesta página podemos encontrar, para além da explicação da matéria, exercícios resolvidos sobre a mesma, aplicações reais possíveis destes algoritmos e os seus applets em Java.

 

 

 

DFS - Depth-First Search (Percurso em profundidade)

 

O método usado pelo DFS é, como o seu nome indica, procurar num grafo o mais profundo possível.

Pode ser definido da seguinte forma:

Algoritmo: marcam-se os nós do grafo para indicar em que medida é que já foram visitados. Para tal começa-se por marcar todos os nós como não tendo sido visitados (Brancos). Os nós que já foram vistos dividem-se em duas categorias: os que estão "em curso" (Verdes) e os que já foram inteiramente vistos (Vermelhos), ou seja, aqueles que se encontram a verde mas que já não tem mais adjacentes brancos.

 

O algoritmo pode ser implementado do seguinte modo:

  1. void DFS (grafo G) {

  2.     for (all v in G) {

  3.         v -> color = branco;

  4.         v -> p = null; }

  5.     time = 0;

  6.     for (all v in G)

  7.         if (v -> color = = branco)

  8.             DFS_Visit (v); }

  9. void DFS_Visit (vértice v) {

  10.     time = time+1;

  11.     v -> color = verde;

  12.     v -> d = time;

  13.     for (all u in v -> adj)

  14.         if (u -> color = = branco) {

  15.             u -> p = v;

  16.             DFS_Visit (u); }

  17.     time = time+1;

  18.     v -> color = vermelho;

  19.     v -> f = time; }

 

O atributo d representa o tempo em que um nó foi visto pela primeira vez, isto é, a altura em que foi marcado a verde.

O atributo f representa o tempo em que o nó foi completamente visitado, neste caso isto significa que todos os seus descendentes foram vistos. Coincide com a altura  em que é marcado a vermelho.

 

As figuras que se seguem ilustram o progresso do algoritmo DFS no grafo que se segue. Este grafo tem como source o vértice I.

  

    O source é o primeiro vértice a ser visitado. Ao ser visitado passa a verde o que quer dizer que o seu "tratamento" está em curso.

    Vamos agora ver o estado dos seus adjacentes. Como I só tem como adjacente o vértice E, e este se encontra a branco, será o próximo a ser tratado, passando então para verde.

    Podemos agora verificar que E tem dois adjacentes e que os dois se encontram a branco. Qual dos dois será o próximo a ser tratado? Devemos adoptar uma convenção para faze-lo, neste caso optamos por percorrer os vértices por ordem alfabética. Assim sendo, o próximo vértice a ser tratado será o B, passando este também a verde.

    Seguindo o "modo de visita" adoptado, vamos tratar o vértice A.

    Neste passo "tratamos" o vértice C que, também ele, tem dois vértices adjacentes ainda a branco. Seguindo a mesma convenção segue-se o vértice D.

    Tratamos agora o vértice D, que passa a verde.

    Segue-se o vértice G.

    Neste passo "tratamos" o vértice F que, como podemos verificar, não tem nenhum adjacente a branco. O que fazer agora?

    Quando um vértice já não tem adjacentes a branco, este considera-se completamente tratado passando então para vermelho.

    Neste momento já temos o vértice F completamente tratado, temos então que recuar para o vértice que foi visitado anteriormente. Neste caso foi o vértice G que como também já não tem adjacentes brancos passa também a vermelho.

    Recuamos então até ao vértice D, mas D ainda tem um adjacente a branco o qual deverá ser tratado.

    H também não tem adjacentes a branco, é então a altura de passar a vermelho.

    Podemos agora verificar que já não existem vértices com adjacentes brancos. Agora temos sempre que ir recuando e tratar os vértices completamente, ou seja, eles vão passar a vermelho, até já não haverem vértices com o "tratamento em curso", isto é, a verde.

    A ultima figura representa o grafo final depois de ser percorrido através do algoritmo de DFS.

 

     

    BFS - Breadth-First Search (Percurso em largura)

     

O Breadth-First Search é um algoritmo que está na base de muitos algoritmos para grafos.

Pode ser definido da seguinte forma:

    Algoritmo: marcam-se os nós do grafo para indicar em que medida é que já foram visitados. Para tal começa-se por marcar todos os nós como não tendo sido visitados (Brancos). Os nós que já foram vistos dividem-se em duas categorias: os que estão "em curso" (Verdes) e os que já foram inteiramente vistos (Vermelhos), ou seja, aqueles que se encontram a verde mas que já não tem mais adjacentes brancos.

     

O algoritmo pode ser implementado do seguinte modo:

    1. void BFS (grafo G, vértice s) {

    2.     queue Q;

    3.     for (all v in G; v != s) {

    4.         v -> color = branco;

    5.         v -> d = inf;

    6.         v -> p = null; }

    7.     s -> color = verde;

    8.     s -> d = 0;

    9.     s -> p = null;

    10.     insert (Q,s);

    11.     while (!empty (Q)) {

    12.         u = head (Q);

    13.         for (all v in u -> adj) {

    14.             if (v -> color = = branco) {

    15.                 v -> color = verde;

    16.                 v -> d = u -> d+1;

    17.                 v -> p = u;

    18.                 insert (Q,v);} }

    19.         delete (Q);

    20.         u -> color = preto; } }

     

As figuras que se seguem ilustram o progresso do BFS no respectivo grafo.

     

    Vamos agora aplicar o algoritmo BFS a este grafo tomando a letra I como source.

    I passa agora a ter o valor 1 a verde, o que quer dizer que a sua visita está "em curso". I é inserido na fila Q e será o próximo vértice a ser tratado.

    Verificamos que o único adjacente de I é  o vértice E e que se encontra a branco . I passa agora para vermelho, pois já foi completamente tratado, e saí da fila. Para esta passa o vértice E (o número 2 indica qual a distancia entre o source  - I - e o vértice - E).

    Como o vértice E tem dois adjacentes a branco, estes vão entrar os dois para a pilha. Devemos adoptar uma convenção para faze-lo. Neste caso os vértices serão colocados na fila por ordem alfabética. O vértice E passa a vermelho pois já está completamente tratado.

    Seguindo o mesmo raciocínio, tratamos o vértice B e o seu adjacente  A passa para a fila com o valor 4. O próximo vértice a ser tratado não será o A mas sim o F pois este agora encontra-se à cabeça da fila.

    Ao tratarmos o vértice F o seu adjacente G, que ainda se encontrava a branco, passa para a fila também com o valor 4 pois esta é a distancia que o separa do source.

    Neste passo A é tratado passando os seu adjacentes, que se encontram a branco, para a fila para serem tratados posteriormente.

    Ao tratarmos G, podemos verificar que os seus vértices não se encontram a branco mas sim a verde, o que quer dizer que o seu "tratamento" está em curso, ou seja, já se encontram na fila.

    Passamos agora ao "tratamento" de C, que se encontra nas mesmas circunstâncias de G, e este saí da fila.

    Ao tratarmos o vértice D, é inserido na fila o ultimo vértice do grafo que se encontra a branco.

    H é tratado e neste momento deparamo-nos com uma fila vazia, o que quer dizer que a execução do algoritmo BFS termina aqui.

    Neste grafo podemos verificar a ordem pela qual se foram visitando os vértices.

     

 

Exercícios resolvidos

(Livro - "Introduction to Algorithms", Thomas H.Cormen; Charles E. Leiserson; Ronald L. Rivest)

 

1. Mostre o resultado da aplicação do algoritmo Depth-First Search no seguinte grafo. Deve seguir a ordem alfabética dos vértices.

 

 2. Mostre o resultado da aplicação do algoritmo Breadth-First Search no seguinte grafo. Deve usar o vértice u como source.

 

 

Mas afinal, quais são as aplicações reais destes dois modos de procura?

Podem ser aplicados, por exemplo, em apoios à decisão, construção de horários, planeamento, demonstração automática de teoremas, ... .

Por exemplo: dado um mapa de estradas, com ligações entre cidades, encontrar um caminho entre duas das cidades. O mapa pode ser representado por um grafo. Um grafo é constituído por nós (A,B,C,D,E,F) e ramos que são as ligações entre os nós.

 

Vamos agora ver como procurar um caminho entre dois pontos (por exemplo A e F) usando as duas estratégias de procura estudadas.

 

DFS - Depth-First Search (Percurso em profundidade)

Partimos de A
Sequência = [
A]
Expansão de A = {
B,E,C}
Nova sequência, com
A marcado = [B,E,C,A]
Escolhemos
B
Expansão de
B = {D,E}
Nova sequência, com
B marcado = [D,E,B,E,C,A]
Escolhemos
D
Expansão de
D = {}
Nova sequência, com
D marcado = [D,E,B,E,C,A]
Escolhemos
E
Expandimos
E = {F}
Nova sequência, com
E (1ª ocorrência) marcado = [F,D,E,B,E,C,A]
Escolhemos
F
F era o nosso objectivo
Então há caminho entre
A e F.

 

BFS - Breadth-First Search (Percurso em largura)

Partimos de A
Sequência = [
A]
Expansão de
A = {B,E,C}
Nova sequência, com
A marcado = [A,B,E,C]
Escolhemos
B
Expansão de
B = {D,E}
Nova sequência, com
B marcado = [A,B,E,C,D,E]
Escolhemos
E
Expansão de
E = {F}
Nova sequência, com
E marcado = [A,B,E,C,D,E,F]
Escolhemos
C
Expansão de
C = {E}
Nova sequência, com
C marcado = [A,B,E,C,D,E,F,E]
Escolhemos
D
Expansão de
D = { }
Nova sequência, com
D marcado = [A,B,E,C,D,E,F,E]
Escolhemos
E
Expansão de
E = {F}
Nova sequência, com
E marcado = [A,B,E,C,D,E,F,F]
Escolhemos
F
F era o nosso objectivo
Então há caminho entre
A e F.


Applet em Java

 

Applet do DFS

 

  To see the applet you need a Java browser!

 

Applet do BFS