Algoritmo A* (A Estrela)
Published: 2024-06-05 11:50:12

**Explicação Teórica**
O Algoritmo A* (lê-se A-Estrela) é um algoritmo de busca em grafos que utiliza funções heurísticas. É amplamente utilizado em problemas de busca, devido à sua eficiência. O objetivo principal do algoritmo é encontrar o caminho mais curto entre dois pontos em um grafo.

O custo total de um nó é dado pela soma de duas funções:

g(x): O custo real do nó inicial até o nó atual.
h(x): A função heurística que estima o custo do nó atual até o nó de destino.
Portanto, a função de custo total (f(x)) é dada por: f(x) = g(x) + h(x)

O Algoritmo A* utiliza essa função de custo para escolher os próximos nós a serem explorados na busca pelo caminho mais curto.

**Principais Características**

**Busca de Melhor-Primeiro**: O A* é uma combinação entre a busca de custo uniforme e a heurística de melhor primeiro. O nó com o menor custo total estimado até o momento é expandido. 

**Função de Avaliação**: Utiliza uma função de avaliação que combina o custo real do caminho percorrido até o momento e uma heurística que estima o custo do caminho restante até o destino. 

**Eficiente**: Em muitos casos, o A* é mais eficiente que a busca cega, como a busca em largura ou em profundidade, pois utiliza informações adicionais sobre o problema para orientar a busca.

**Introdução ao problema**

Imagine que queremos encontrar o caminho mais curto para viajar por algumas cidades em São Paulo, com o objetivo final de chegar a uma cidade específica.
Cada cidade possui uma distância (em quilômetros) até as outras cidades, fornecendo um contexto realista para aplicar o algoritmo A*.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9gwmanjturzoxodw5y2l.png)

Desenvolvmemos um Algoritimo em Python para resolução desse problema o repositorio do projeto se encontra aqui:
https://github.com/CristianoMafraJunior/A_Estrela

**Explicação da solução**:

```
class A_Estrela:
    def __init__(self):
        self.cidades = {
            "Araçatuba": {"Votuporanga": 127, "Novo Horizonte": 183, "Marília": 170},
            ...
        }

        self.heuristica = {
            "São Paulo": 0,
            "Santos": 50,
            ...
        }
```
**Inicialização da Classe A_Estrela**

cidades: Um dicionário que representa o grafo das cidades e as distâncias entre elas. Cada cidade tem como valor outro dicionário, onde as chaves são cidades vizinhas e os valores são as distâncias para essas cidades vizinhas.

heuristica: Um dicionário que representa a estimativa de distância (heurística) de cada cidade até o destino final (São Paulo).

**Método a_estrela**:

```
def a_estrela(self, inicio, destino):
    fila_prioridade = []
    heapq.heappush(fila_prioridade, (0, inicio))
    visitados = set()
    caminho_percorrido = {inicio: None}
    custo_total = {cidade: float("inf") for cidade in self.cidades}
    custo_total[inicio] = 0

    while fila_prioridade:
        custo_atual, cidade_atual = heapq.heappop(fila_prioridade)

        if cidade_atual == destino:
            caminho = self.reconstruir_caminho(caminho_percorrido, destino, custo_total)
            return custo_total[destino], caminho

        if cidade_atual in visitados:
            continue

        visitados.add(cidade_atual)

        for vizinho, custo in self.cidades[cidade_atual].items():
            novo_custo = custo_total[cidade_atual] + custo
            custo_heuristica = self.heuristica[vizinho]
            if novo_custo < custo_total[vizinho]:
                custo_total[vizinho] = novo_custo
                heapq.heappush(fila_prioridade, (novo_custo + custo_heuristica, vizinho))
                caminho_percorrido[vizinho] = cidade_atual

    return float("inf"), []

```

Fila de Prioridade: fila_prioridade é uma fila de prioridade (min-heap) usada para sempre expandir o nó com o menor custo estimado (custo real + heurística). Inicialmente, ela contém o nó inicial com custo 0.

Conjuntos e Dicionários:

visitados: Um conjunto para rastrear as cidades já visitadas.
caminho_percorrido: Um dicionário para manter o caminho percorrido até cada cidade.

custo_total: Um dicionário para manter o menor custo encontrado até cada cidade. Inicialmente, todos os custos são definidos como infinito (float("inf")), exceto o custo da cidade inicial, que é 0.

Loop Principal: Enquanto houver cidades na fila de prioridade:

Remove a cidade com o menor custo estimado da fila.

Se essa cidade é o destino, reconstrói o caminho usando reconstruir_caminho e retorna o custo total e o caminho.

Se a cidade já foi visitada, pula para a próxima iteração.

Marca a cidade como visitada e avalia seus vizinhos.

Calcula o novo custo para cada vizinho e, se for menor que o custo previamente registrado, atualiza o custo total, adiciona o vizinho na fila de prioridade com o novo custo estimado e registra o caminho percorrido.

**Método reconstruir_caminho**:

```
def reconstruir_caminho(self, caminho_percorrido, destino, custo_total):
    caminho = []
    cidade = destino
    while cidade is not None:
        caminho.append((cidade, custo_total[cidade], self.heuristica[cidade], custo_total[cidade] + self.heuristica[cidade]))
        cidade = caminho_percorrido[cidade]
    return list(reversed(caminho))
```

**Reconstrução do Caminho**: 
Reconstrói o caminho desde o destino até o início, usando o dicionário caminho_percorrido. Cada cidade é registrada junto com seu custo atual, heurística e custo total (custo + heurística). O caminho é invertido no final para obter a ordem correta (do início ao destino).

**Uso do algoritimo**:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1hki850ncx5aahdnsl3.png)