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

arrow_back Aula 10 - Descoberta de caminho – Parte 04

2 - Menor caminho: algoritmo de Dijkstra

O Dijkstra é um dos algoritmos clássicos da área de grafos. Ele é comumente utilizado em situações em que se deseja encontrar o menor caminho de um vértice origem para todos os outros vértices do grafo, mas também pode ser adaptado para encontrar o menor caminho entre dois vértices específicos. Como? É só parar ele no meio do caminho.

O algoritmo é baseado nas técnicas de programação dinâmica para obter uma boa performance. Calma, eu não vou explicar em detalhes o que é programação dinâmica, isso em si daria uma disciplina de 60 horas do curso! A ideia bem simplificada (mas olhe, é BEM simplificada) é salvar e reutilizar resultados que você já encontrou uma vez na execução do algoritmo, para não ter de calcular de novo. O algoritmo vai “lembrando” dos resultados e, quando precisa de algo que ele já sabe, por exemplo, o custo para chegar em um vértice que ele já calculou não precisa refazer as contas, apenas usa o que já está armazenado. A ideia é simples, a implementação não.

O algoritmo de Dijkstra segue o seguinte fluxo:

  • Passo 1: Todos os vértices do grafo são considerados como não visitados;
  • Passo 2: Uma distância é colocada para cada vértice. A origem do caminho tem distância 0 (você não gasta nada para chegar lá, é seu ponto de partida!), enquanto os outros vértices têm distância infinita (você ainda não chegou em nenhum deles). Além disso, para cada vértice vai ter uma informação anterior, para informar a partir de que outro ponto do grafo o caminho até ele foi construído;
  • Passo 3: O vértice origem passa a ser seu vértice atual de análise, onde você vai realizar os seguintes passos:
    • Atribuir distância a todos os vizinhos não visitados. Essa distância é o custo de chegar até o vizinho (peso da aresta) mais o custo para chegar até o vértice atual. Caso já exista uma distância diferente de infinito, ficará a menor entre a que já existia e a que acabou de ser calculada;
    • Caso a distância atual seja a menor, alterar o vértice anterior do vizinho para o vértice atual.
  • Passo 4: O vértice atual é marcado como visitado;
  • Passo 5: Um novo vértice não visitado é escolhido para ser o vértice atual, e os procedimentos do passo 3 são repetidos até não existirem mais vértices não visitados.

Uma característica interessante desse algoritmo é que ele testa vários caminhos até cada vértice, mas salva apenas o menor que foi encontrado ao longo da execução. Por exemplo, para um vértice X, se ele salva o menor custo de chegar a X, então todos os caminhos que passam por X usarão esse custo. Ele não precisa ficar recalculando esse valor toda vez que encontrar um caminho que passa por X, ele apenas utiliza o que já foi calculado anteriormente. No Código 01 pode-se ver o pseudocódigo do algoritmo.

É difícil visualizar como ele funciona, não é mesmo? Imagine como se fosse uma busca sistemática, só que ela vai salvando os valores do caminho encontrado, e não precisa retornar em nenhum momento. É quase como se o caminho fosse uma “onda” que vai varrendo os vértices do grafo e atualizando com os menores caminhos possíveis, até chegar ao ponto de origem. Veja um exemplo com o mapa da seção anterior em que você partirá do mesmo ponto.

Assuma que o custo de movimentação entre os blocos é 1, e não há diferença entre movimento vertical e horizontal (da mesma forma que foi feito no exemplo anterior):

Voltando ao cenário!
Passo 01 do patrulheiro estelar

Foi processado o primeiro vértice (origem, marcado de verde) e atualizadas as distâncias dos vizinhos a partir dele. Esse processo será repetido até chegar no vértice destino.

Passo 02 do patrulheiro estelar

Os vértices visitados passam a ser coloridos de vermelho e não serão mais explorados (o valor contido já é o menor caminho possível até ele). O vértice atual em exploração continua marcado como verde.

Passo 03 do patrulheiro estelar
Passo 04 do patrulheiro estelar
Passo 05 do patrulheiro estelar
Passo 06 do patrulheiro estelar
Passo 07 do patrulheiro estelar
Passo 08 do patrulheiro estelar
Passo 09 do patrulheiro estelar
Passo 10 do patrulheiro estelar
Passo 11 do patrulheiro estelar
Passo 12 do patrulheiro estelar

A partir de agora, eu vou “queimar” algumas etapas na sequência senão o pessoal que faz as imagens vai desmaiar de exaustão! Você verá a exploração de vários vértices simultaneamente, mas a ideia é repetir o processo de um por um. Dessa forma tem-se garantia de o caminho até o vértice será sempre o menor!

Passo 13 do patrulheiro estelar

Todos os vértices em uma distância de 3 que tem vizinhos.

Passo 14 do patrulheiro estelar

Todos os vértices que percorreram uma distância de 4 casas.

Passo 15 do patrulheiro estelar

Todos os vértices que percorreram uma distância de 5.

Passo final!!

Ao explorar todos os vértices que percorreram uma distância de 6, você encontrará um que chega até o destino. Como todas as distâncias são unitárias, tem-se a garantia de que esse primeiro que chegou é o menor caminho, que seguindo o algoritmo pode ser expresso pelo seguinte desenho:

Chegada ao destino!

Perceba que existem mais dois caminhos que possuem o mesmo custo: o que passa por (4,2) e o que passa por (5,2). Qualquer um desses três caminhos satisfazem a condição de ser o menor caminho entre o bloco (4,1) e o bloco (4,8).

Demarcação do menor caminho entre o bloco (4,1) e o bloco (4,8)

Para esse mapa do exemplo, não é possível ver tanta vantagem no algoritmo de Dijkstra frente às outras buscas. Porém, quando o cenário começa a ficar mais complexo, ele torna-se mais eficaz do que as buscas para a solução do caminho:

  • Mapas com custos distintos de movimentação (por exemplo, terrenos diferentes);
  • Mapas com layout mais intrincado, cheios de obstáculos.

Mesmo assim, esse algoritmo ainda precisa visitar um grande número de vértices para achar a resposta correta. Sem entrar em muitos detalhes de complexidade de algoritmos, suponha que, se o seu mapa tiver 5000 blocos, ele ainda quebra bem o galho. Mas em um mapa com 1.000.000 de blocos, aí os seus caminhos iam demorar muito tempo para serem calculados! E não é difícil pensar mapas grandes, dado o tamanho de alguns cenários em jogos atuais! Por esse motivo, o algoritmo de Dijkstra tradicional não é tão usado para achar o caminho entre dois pontos, mas sim uma otimização que foi criada a partir dele: o algoritmo A*.

Versão 5.3 - Todos os Direitos reservados