Cursos / Jogos Digitais / Inteligência Artificial para Jogos / Aula

arrow_back Aula 09 - Descoberta de caminho – Parte 03

3 - Busca em Largura

Retome o exemplo da viagem de Santa Cruz para Pau dos Ferros, agora para entender o outro tipo de busca.

Na busca em largura, ou BFS, a ideia é que você explore todos os vizinhos do vértice em uma mesma profundidade antes de verificar os próximos vértices. No fim das contas você vai realizar os mesmos processamentos, só que em uma ordem diferente! Essa busca é mais sistemática e explora primeiro os vértices que estão mais próximos da origem, dirigindo-se gradativamente aos vértices mais distantes. É a ideia oposta da busca em profundidade: ao invés de tentar achar logo um caminho, ele vai verificando todos os caminhos do grafo até encontrar um que seja o destino desejado.

Figura 16 - Essa sim parece uma busca segura!
Essa sim parece uma busca segura!

Para implementar essa ideia, parta para uma abordagem iterativa, utilizando uma Fila (lembra da Aula 03? A mesminha! ). Você vai pegar seu vértice de origem, Santa Cruz, e vai sair adicionando todos os vizinhos dele: Mossoró, Natal e Caicó. Todos esses vértices serão colocados em uma fila para serem processados. Após isso, o primeiro que foi colocado na fila torna-se o atual, e os seus vizinhos que ainda não estão na fila serão então inseridos no final. Perceba que esses novos vizinhos só serão processados quando todos os três vizinhos de Santa Cruz forem avaliados.

Dessa forma, você vai fazendo uma varredura do grafo pelos níveis de vértices que estão mais próximos do ponto de origem, e os nós mais distantes serão atingidos no final do algoritmo. Essa abordagem é boa para momentos em que você quer encontrar o primeiro caminho possível com o menor número de percursos no grafo (isso faz mais sentido quando o grafo não tem peso, em que o número de arestas do caminho seria equivalente ao seu custo!). O pseudocódigo desse algoritmo é ilustrado pelas linhas do Código 03 abaixo:

x
1
Função BFS(int inicial, int destino){
2
    Fila próximos
3
4
    Insira o nó inicial na fila
5
6
    Enquanto a fila não está vazia{
7
        Processa o próximo da fila
8
        Retira o próximo elemento da fila
9
        Marca o elemento como visitado
10
11
        Para cada vizinho do próximo{
12
            Insere o vizinho na fila
13
            Marca o vizinho como visitado
14
        }
15
    }
16
}

Hum… ainda está confuso, hein? Já sei, já sei, você quer as imagens... No caso da busca em largura, os vértices em amarelo vão representar os vértices inseridos na fila de avaliação pelo algoritmo, enquanto os outros mantêm o mesmo significado do que foi anteriormente visto no algoritmo DFS.

Figura 17 - Ponto inicial da BFS
Ponto inicial da <span class='strong'>BFS</span>

Como você pôde observar na Figura 16, o caminho final dessa iteração foi chegar a Pau dos Ferros através de Caicó, vértice a partir do qual Pau dos Ferros foi inserido na fila. Da mesma forma que o algoritmo anterior, vou colocar um exemplo de implementação utilizando a linguagem C# (Código 04). Esse exemplo busca o primeiro caminho possível de um ponto inicial até um ponto final.

89
1
using System.Collections;
2
using System.Collections.Generic;
3
using UnityEngine;
4
5
public class Algoritmos : MonoBehaviour {
6
    //matriz de adjacência para o grafo RN
7
    private int [,] grafo = new int[5,5] { {0, 40, 10, 20, 45},
8
                             {40, 0, 40, 30, 10},
9
                             {10, 40, 0, 8, -1},
10
                             {20, 30, 8, 0, 35},
11
                             {45, 10, -1, 35, 0}};
12
13
    //Nós que já foram visitados, inicia todos como não visitados
14
    private int[] visitados = new int[5]{0,0,0,0,0};
15
16
    //Nomes das cidades
17
   string[] cidades = new string[5]{"Natal", "Mossoró", "Santa Cruz", "Caicó", "Pau dos Ferros"};
18
19
    //Nó inicial - 0 representa Natal
20
    private int inicio = 0;
21
22
    //Nó final - 3 representa Caicó
23
    private int fim = 3;
24
25
    string caminho = "";
26
27
    void Start () {
28
     print("COMECANDO A BUSCA");
29
     Busca(inicio, fim);
30
    }
31
    
32
    // Update is called once per frame
33
    void Update () {}
34
35
    
36
    //Busca
37
    void Busca(int start, int finish){
38
     //Em largura
39
     //BFS(start, finish)    
40
    }
41
42
//Algoritmo BFS, montando para parar no primeiro caminho que encontrar
43
    void BFS(int atual, int alvo){
44
     //Fila de nós a serem processados
45
     Queue<int> fila = new Queue<int>();
46
     int prox, cont = 0;
47
48
     //Coloca o nó inicial na fila e marca como visitado
49
     fila.Enqueue(atual);
50
     visitados[atual] = 1;
51
52
     //Enquanto a fila não secar
53
     while(fila.Count > 0){
54
         //Pega o próximo na fila
55
         prox = fila.Peek();
56
         visitados[prox] = 1;
57
58
         if(cont != 0)
59
             caminho += " -> ";
60
61
         caminho += cidades[prox];
62
63
         //Remove ele da fila
64
         fila.Dequeue();
65
66
         //Coloca os vizinhos na fila, e marca como visitado
67
         for(int i=0; i<5; i++){
68
             if(grafo[prox, i] > 0 && visitados[i] == 0){
69
                 if(i == alvo)
70
                     caminho += " -> " + cidades[i];
71
                 else{
72
                     fila.Enqueue(i);
73
                 }
74
75
                 visitados[i] = 1;
76
             }
77
         }
78
79
         //Se chegou no fim
80
         if(visitados[alvo] == 1)
81
             break;
82
83
         //Atualiza contador auxiliar da impressão
84
         cont++;
85
    }
86
87
     //Imprime o primeiro caminho que encontrou
88
     print(caminho);
89
}

E com isso você já fica com duas ferramentas poderosas para trabalhar. Apesar de suas aplicações na área de jogos, esses algoritmos não se limitam apenas a esse campo, podendo ser encontrados na resolução de diversos problemas na área de redes, circuitos, otimização de algoritmos, engenharia de software e vários outros campos da computação! Então é muito importante entender a ideia de funcionamento e brincar com a implementação desses algoritmos para aumentar o seu arsenal como programador.

Perceba uma coisa: com esses algoritmos você consegue verificar se existe um caminho de A até B, e até achar todos os caminhos que existem entre os dois! Mas eles não são direcionados para achar o menor caminho: aquele que te leva com o menor custo possível de um ponto a outro - a principal problemática que foi discutida no início das aulas de “Descoberta de caminhos”!

Não se desespere! Mature um pouco o conteúdo dessa aula, que na próxima você terá o gran finale! Conhecerá dois algoritmos voltados para essas tarefas: o algoritmo de Djikstra e o algoritmo A*!

Não consegue conter a emoção, hein? Clique aqui para ver o próximo episódio.

Até lá!

Versão 5.3 - Todos os Direitos reservados