Optimización del problema del viajante: solución visual de recocido simulado y algoritmo de 2 opciones implementado en C++

Primera parte: Introducción

El problema del viajante (TSP) es uno de los problemas clásicos de la optimización combinatoria, cuyo objetivo es encontrar la ruta de viaje más corta para que un viajante pueda visitar cada ciudad una vez y regresar al origen. Dado que TSP es un problema NP-difícil, es muy difícil encontrar su solución óptima. Sin embargo, al utilizar heurísticas como el recocido simulado y la opción 2, podemos encontrar soluciones aproximadas razonablemente buenas.

Este artículo lo guiará sobre cómo implementar ambos métodos usando C++ y le proporcionará una herramienta de visualización para representar visualmente la solución TSP. También exploraremos los principios de recocido simulado y 2-opt, y cómo se pueden aplicar a TSP.

Breve descripción del problema del viajante de comercio.

TSP se puede definir matemáticamente de la siguiente manera: dada una lista de ciudades y la distancia entre cada par de ciudades, encuentre una ruta de viaje con la distancia total más corta de modo que cada ciudad sea visitada solo una vez y regrese a la ciudad inicial al final.

Introducción al algoritmo de recocido simulado.

El algoritmo de recocido simulado está inspirado en el proceso de enfriamiento de sólidos. En física, el proceso de enfriamiento de la materia es aleatorio, pero con el tiempo esta aleatoriedad disminuye y el sistema tiende a un estado más ordenado. El algoritmo de recocido simulado simula este proceso y encuentra la solución óptima aproximada al problema reduciendo continuamente la aleatoriedad en el espacio de búsqueda.

Los pasos básicos del algoritmo son los siguientes:

  1. Inicialice una solución y una temperatura alta.
  2. A la temperatura actual, repita los siguientes pasos N veces:
    • Seleccione aleatoriamente una solución vecina.
    • Calcule la diferencia de energía entre la nueva solución y la solución actual.
    • Si la nueva solución tiene menor energía o cumple ciertas condiciones de probabilidad, se acepta la nueva solución.
  3. Baje la temperatura y repita el paso 2 hasta que se cumpla la condición de parada.

Introducción al algoritmo de 2 opciones.

2-opt es una estrategia de búsqueda local simple pero eficaz para TSP. La idea básica es encontrar dos aristas en la ruta de viaje actual e intercambiar sus puntos finales para obtener una nueva ruta de viaje.

Los pasos básicos de 2-opt son los siguientes:

  1. La solución inicial se elige al azar.
  2. Encuentre dos aristas en la solución actual e intercambie sus puntos finales para obtener una ruta más corta.
  3. Repita el paso 2 hasta que no encuentre bordes que puedan mejorarse.

Implementación de C++

Primero, definimos la estructura de datos de TSP:

struct City {
    
    
    int id;
    double x, y;
};

double distance(const City& c1, const City& c2) {
    
    
    return sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y));
}

std::vector<City> cities;

Ahora, necesitamos una función para calcular la distancia total de una ruta de viaje determinada:

double totalDistance(const std::vector<int>& tour) {
    
    
    double dist = 0.0;
    for (size_t i = 0; i < tour.size() - 1; ++i) {
    
    
        dist += distance(cities[tour[i]], cities[tour[i+1]]);
    }
    dist += distance(cities[tour.back()], cities[tour[0]]);
    return dist;
}

En este punto, hemos establecido la base para implementar el recocido simulado y 2-opt. Para conocer el proceso de visualización e implementación de algoritmos específicos, descargue el proyecto completo para verlo.

Parte 2: implementación de recocido simulado

Comencemos a implementar el algoritmo de recocido simulado. La siguiente es la estructura básica del algoritmo:

std::vector<int> simulatedAnnealing(const std::vector<City>& cities, double initialTemperature, double coolingRate, int iterations) {
    
    
    int n = cities.size();
    std::vector<int> currentTour(n);
    std::iota(currentTour.begin(), currentTour.end(), 0);  // 初始化为0, 1, ..., n-1

    std::vector<int> bestTour = currentTour;
    double bestDist = totalDistance(bestTour);

    std::default_random_engine generator;
    std::uniform_real_distribution<double> distribution(0.0, 1.0);

    double temperature = initialTemperature;

    for (int i = 0; i < iterations; ++i) {
    
    
        std::vector<int> newTour = currentTour;
        
        // 随机选择两个城市并交换它们
        int a = rand() % n;
        int b = rand() % n;
        std::swap(newTour[a], newTour[b]);

        double currentDist = totalDistance(currentTour);
        double newDist = totalDistance(newTour);
        double deltaE = newDist - currentDist;

        // 如果新旅程更短或在某个概率下接受更长的旅程
        if (deltaE < 0 || distribution(generator) < exp(-deltaE / temperature)) {
    
    
            currentTour = newTour;
            currentDist = newDist;
        }

        if (currentDist < bestDist) {
    
    
            bestTour = currentTour;
            bestDist = currentDist;
        }

        temperature *= coolingRate;
    }

    return bestTour;
}

Implementación de 2 opciones

A continuación, implementaremos el algoritmo de 2 opciones:

std::vector<int> twoOpt(const std::vector<City>& cities, const std::vector<int>& initialTour) {
    
    
    int n = cities.size();
    std::vector<int> tour = initialTour;

    bool improved = true;
    while (improved) {
    
    
        improved = false;
        double bestDist = totalDistance(tour);

        for (int i = 0; i < n - 1; ++i) {
    
    
            for (int j = i + 1; j < n; ++j) {
    
    
                std::vector<int> newTour = tour;
                std::reverse(newTour.begin() + i, newTour.begin() + j);

                double newDist = totalDistance(newTour);
                if (newDist < bestDist) {
    
    
                    tour = newTour;
                    bestDist = newDist;
                    improved = true;
                }
            }
        }
    }

    return tour;
}

combinar los dos

Para obtener mejores resultados, primero podemos utilizar el algoritmo de recocido simulado para obtener una buena solución inicial y luego aplicar 2-opt para una mayor optimización.

std::vector<int> combinedTSPSolution(const std::vector<City>& cities) {
    
    
    auto initialSolution = simulatedAnnealing(cities, 10000, 0.995, 100000);
    return twoOpt(cities, initialSolution);
}

Este enfoque combinado a menudo produce mejores soluciones que cualquiera de los algoritmos por separado.


En la siguiente sección, exploraremos cómo agregar capacidades de visualización al algoritmo anterior, lo que nos permitirá ver el progreso de la solución en tiempo real y analizar cómo optimizar y ajustar aún más los parámetros del algoritmo para obtener mejores resultados. Además, le proporcionaremos un enlace de descarga para el proyecto completo.

Parte 3: visualización y optimización adicional

Visualización

Para visualizar eficazmente las soluciones TSP, podemos utilizar bibliotecas de gráficos C++ como SFML o SDL. Aquí presentaremos brevemente cómo utilizar la biblioteca SFML para visualizar el progreso de una solución.

Primero, necesitas instalar la biblioteca SFML. Luego podemos crear una ventana simple para dibujar ciudades y caminos.

#include <SFML/Graphics.hpp>

void visualizeTSPSolution(const std::vector<City>& cities, const std::vector<int>& tour) {
    
    
    sf::RenderWindow window(sf::VideoMode(800, 600), "TSP Visualization");
    window.clear(sf::Color::White);

    sf::CircleShape circle(5);
    circle.setFillColor(sf::Color::Blue);

    // 绘制城市
    for (const auto& city : cities) {
    
    
        circle.setPosition(city.x, city.y);
        window.draw(circle);
    }

    // 绘制路径
    sf::VertexArray lines(sf::LinesStrip, cities.size() + 1);
    for (size_t i = 0; i < tour.size(); ++i) {
    
    
        lines[i].position = sf::Vector2f(cities[tour[i]].x, cities[tour[i]].y);
        lines[i].color = sf::Color::Red;
    }
    lines[tour.size()].position = sf::Vector2f(cities[tour[0]].x, cities[tour[0]].y);  // Closing the loop
    lines[tour.size()].color = sf::Color::Red;

    window.draw(lines);
    window.display();

    while (window.isOpen()) {
    
    
        sf::Event event;
        while (window.pollEvent(event)) {
    
    
            if (event.type == sf::Event::Closed)
                window.close();
        }
    }
}

Esta función de visualización mostrará la ciudad y la mejor ruta actual. En cada iteración de recocido simulado o 2-opt, puede llamar a esta función para actualizar la ruta mostrada.

optimización avanzada

  1. Ajuste de parámetros : el efecto del recocido simulado depende en gran medida de sus parámetros, como la temperatura inicial, la velocidad de enfriamiento y el número de iteraciones. Es posible que deba realizar ajustes según el tamaño y la complejidad del problema.

  2. Otros métodos heurísticos : además del recocido simulado y la opción 2, existen muchos otros métodos heurísticos, como algoritmos genéticos, optimización de colonias de hormigas, etc., que se pueden utilizar para resolver TSP.

  3. Computación paralela : dado que el recocido simulado y la opción 2 son métodos basados ​​en iteraciones, se pueden paralelizar fácilmente para aumentar la velocidad. Puede considerar el uso de subprocesos múltiples o aceleración de GPU para mejorar aún más el rendimiento de su algoritmo.

conclusión

TSP es uno de los problemas de optimización combinatoria más estudiados y tiene muchas aplicaciones prácticas como logística, fabricación y aviación. Aunque TSP es un problema NP-difícil, mediante el uso de recocido simulado, 2-opt y otras heurísticas, podemos obtener muy buenas soluciones aproximadas en tiempo real.

Supongo que te gusta

Origin blog.csdn.net/qq_38334677/article/details/132606939
Recomendado
Clasificación