Aplicação do Problema do Caixeiro Viajante na Otimização de Rotas entre Capitais Brasileiras ✈️

Filipe Costa

Francisco Sousa

Universidade Federal do Piauí

Vinicios Castro

Introdução

Definição simples:

Um caixeiro precisa visitar n cidades exatamente uma vez cada e voltar para a cidade de origem, percorrendo a menor distância (ou custo) total possível.

  • É representado como um grafo completo ponderado (todas as cidades têm ligação direta com todas as outras, com um custo/distância).

  • O objetivo é encontrar o ciclo hamiltoniano de menor custo.

Algoritmo

Algoritmo: Caixeiro Viajante

Entrada: Matriz de distâncias entre n elementos.

Inicializar:

    Conjunto de pontos = {1,2,...,n}

    MelhorCusto=∞

    MelhorRota=[ ]

Para cada permutação P de pontos:

    a. Calcular Custo(P)

    b. Se Custo(P)<MelhorCusto:

        MelhorCusto=Custo(P)

Implementação da Instância do Problema

  • O trabalho propõe a implementação e análise computacional de uma instância do Problema do Caixeiro Viajante aplicada ao contexto de deslocamento entre capitais brasileiras.

Estrutura do Grafo e Coordenadas

O problema foi modelado como um grafo completo \(G=(V,E)\), onde:

  • \(V\) representa o conjunto de vértices (capitais)
  • \(E\) representa o conjunto de arestas (ligações entre capitais)
  • Cada aresta possui um peso associado correspondente à distância entre duas capitais

As coordenadas geográficas de cada capital foram utilizadas para calcular as distâncias entre os vértices por meio da fórmula de Haversine(D), que permite estimar a distância entre dois pontos na superfície terrestre.

\[ d = 2R \arcsin \left( \sqrt{ \sin^2 \left( \frac{\varphi_2 - \varphi_1}{2} \right) + \cos(\varphi_1) \cos(\varphi_2) \sin^2 \left( \frac{\lambda_2 - \lambda_1}{2} \right) } \right) \]

Estrutura do Grafo e Coordenadas

Estrutura do Grafo e Coordenadas

Dessa forma, o grafo gerado é:

  • Completo (todas as capitais estão conectadas entre si)
  • Ponderado (cada conexão possui um custo)
  • Não direcionado (distâncias simétricas)

Implementação do Algoritmo de Referência (BASELINE)

A abordagem segue os seguintes passos:

  1. Fixação da cidade inicial (Brasília);

  2. Geração de todas as permutações possíveis das demais capitais;

  3. Construção das rotas completas (ida e volta);

  4. Cálculo da distância total de cada rota;

  5. Seleção da rota com menor custo

Máquina

Configurações do máquina usada:

  • Sistema Operacional: macOS
  • Processador: Apple M4
  • Memória RAM: 16 GB(memória unificado)
  • Armazenamento: 256 GB SSD

Implementação e Ambiente de Execução

import pandas as pd
import itertools
import time
import math

# Configurações do Problema
ARQUIVO_CSV = "matriz_distancias_capitais_brasil.csv"
CAPITAL_INICIAL = "Brasília"
QUANTIDADE_CAPITAIS = 5  # Quantidade de capitais mais próximas de Brasília
TEMPO_LIMITE_MINUTOS = 180
print("=" * 70)
print("CAIXEIRO VIAJANTE - FORÇA BRUTA COM TEMPO LIMITE")
print("=" * 70)

print("\n[1] Carregando matriz de distâncias...")
matriz = pd.read_csv(ARQUIVO_CSV, index_col=0)
print(f"Arquivo carregado! Total de capitais disponíveis: {len(matriz)}")

print("\n[2] Selecionando capitais mais próximas...")
distancias_brasilia = matriz.loc[CAPITAL_INICIAL].drop(CAPITAL_INICIAL)
capitais_proximas = distancias_brasilia.sort_values().head(QUANTIDADE_CAPITAIS)
capitais_para_visitar = list(capitais_proximas.index)

for i, capital in enumerate(capitais_para_visitar, start=1):
    print(f"{i}. {capital} - {capitais_proximas[capital]} km de Brasília")

total_possibilidades = math.factorial(QUANTIDADE_CAPITAIS)
print(f"\nTotal de rotas possíveis: {total_possibilidades:,}")
def calcular_distancia_rota(rota, matriz_distancias):
    """Calcula a distância total de uma rota (ida e volta)."""
    distancia_total = 0
    for i in range(len(rota) - 1):
        origem = rota[i]
        destino = rota[i + 1]
        distancia_total += matriz_distancias.loc[origem, destino]
    return distancia_total
print("\n[4] Iniciando busca por força bruta...")
tempo_limite_segundos = TEMPO_LIMITE_MINUTOS * 60
inicio_tempo = time.time()
menor_distancia = float("inf")
melhor_rota = None
rotas_testadas = 0

for rota_permutada in itertools.permutations(capitais_para_visitar):
    tempo_atual = time.time()
    tempo_decorrido = tempo_atual - inicio_tempo
    
    # Condição de parada por tempo
    if tempo_decorrido >= tempo_limite_segundos:
        print("\nTempo limite atingido.")
        break
    
    # Monta a rota completa: Brasília -> Capitais -> Brasília
    rota_completa = [CAPITAL_INICIAL] + list(rota_permutada) + [CAPITAL_INICIAL]
    distancia_total = calcular_distancia_rota(rota_completa, matriz)
    rotas_testadas += 1
    
    # Atualiza se encontrar uma rota mais curta
    if distancia_total < menor_distancia:
        menor_distancia = distancia_total
        melhor_rota = rota_completa
        print(f"\nNova melhor rota encontrada! Distância: {menor_distancia:.2f} km")
        print(" -> ".join(melhor_rota))

    # Log de progresso a cada 10.000 iterações
    if rotas_testadas % 10000 == 0:
        percentual = (rotas_testadas / total_possibilidades) * 100
        print(f"Progresso: {percentual:.4f}% | Testadas: {rotas_testadas:,}")
fim_tempo = time.time()
tempo_total = fim_tempo - inicio_tempo

print("\n" + "=" * 70)
print("RESULTADO FINAL")
print("=" * 70)
print(f"Rotas testadas: {rotas_testadas:,} ({ (rotas_testadas/total_possibilidades)*100:.4f}%)")
print(f"Tempo real de execução: {tempo_total:.2f} segundos")

Implementação do Algoritmo de Referência (BASELINE)

Análise de Complexidade

O algoritmo de força bruta possui complexidade temporal:

\[ O(n!) \]

É necessário calcular a soma das distâncias entre as cidades, o que possui custo:

\[ O(n) \]

Assim, a complexidade total pode ser representada como:

\[ O(n . n!) \]

Implementação do Algoritmo de Referência (BASELINE)

Resultados

Análise de complexidade e tempo de execução das rotas.
Teste Nº de Capitais Nº de Possibilidades Rotas Testadas % do Espaço Tempo (s)
1 5 120 120 100% 0,01
2 8 40.320 40.320 100% 2,09
3 9 362.880 362.880 100% 21,27
4 10 3.628.800 3.628.000 100% 232,53
5 13 6.227.020.800 20.892.479 0,3355% 1.800
6 27 1,09 × 10²⁸ 67.081.582 ~0,0000% 10.800

Implementação do Algoritmo de Referência (BASELINE)

Resultados

Implementação do Algoritmo de Referência (BASELINE)

Taxa de Processamento

Podemos estimar a taxa média de processamento: Exemplo (teste 13 capitais):

\[ \frac{20892479}{1800} = 11606 \text{ rotas/segundo} \]

Mesmo com essa taxa seriam necessários bilhões de anos para explorar todo o espaço das 27 capitais.

Obrigado