Problema del vendedor ambulante: Python, C++ Algoritmo

โšก Resumen inteligente

El problema del viajante es una tarea de optimizaciรณn clรกsica de complejidad NP-difรญcil que consiste en encontrar el recorrido mรกs corto que visite cada ciudad exactamente una vez y regrese al punto de origen, utilizando datos de distancia proporcionados a travรฉs de un grafo.

  • ๐Ÿ—บ๏ธ Planteamiento del problema: Dado un grafo ponderado de ciudades y distancias entre pares de ellas, encuentre el ciclo hamiltoniano de costo mรญnimo que comienza y termina en la misma ciudad de origen.
  • โš™๏ธ Familias de soluciones: La fuerza bruta enumera todos los n! โ€‹โ€‹recorridos, la ramificaciรณn y acotaciรณn poda la bรบsqueda, la programaciรณn dinรกmica almacena en cachรฉ los subproblemas y el vecino mรกs cercano ofrece una heurรญstica rรกpida.
  • ๐Ÿ“‰ Programaciรณn dinรกmica: El costo de recurrencia de Held-Karp (i, S, j) reutiliza los caminos mรกs cortos a travรฉs de subconjuntos de vรฉrtices y proporciona una soluciรณn exacta en tiempo O(Nยฒ ยท 2^N).
  • ๐Ÿ’ป Code Ejemplos: El tutorial funcionaba perfectamente. C++ y Python Implementaciones que calculan el coste รณptimo de un recorrido para una matriz de adyacencia de cuatro ciudades.
  • ๐ŸŒ Aplicaciones: Las variantes del TSP incluyen la optimizaciรณn de rutas de suministro de energรญa, la perforaciรณn de placas de circuito impreso, la secuenciaciรณn de ADN, la programaciรณn de telescopios y la planificaciรณn de rutas de recogida en almacenes.
  • ๐Ÿค– Perspectiva de la IA: El aprendizaje por refuerzo moderno, las redes neuronales grรกficas y las heurรญsticas como Lin-Kernighan y Concorde resuelven instancias a gran escala del problema del viajante que se utilizan en el sector logรญstico.

Problema de vendedor ambulante

ยฟQuรฉ es el problema del viajante (TSP)?

El problema del viajante (TSP, por sus siglas en inglรฉs) es un problema clรกsico de optimizaciรณn combinatoria en la informรกtica teรณrica. Dado un grafo de ciudades, el TSP busca el camino mรกs corto que visite cada nodo exactamente una vez y regrese a la ciudad de origen.

El enunciado del problema proporciona una lista de ciudades junto con las distancias entre cada par de ciudades.

Objetivo: Empieza en la ciudad de origen, visita todas las demรกs ciudades exactamente una vez y regresa a la ciudad de partida. El objetivo es encontrar la ruta de ida y vuelta mรกs corta posible.

Ejemplo de TSP

Consideremos el grรกfico que aparece a continuaciรณn, donde 1, 2, 3 y 4 representan las ciudades, y el peso de cada arista representa la distancia entre esas ciudades.

Ejemplo de TSP

El objetivo es encontrar la ruta mรกs corta posible que comience en la ciudad de origen, visite todas las demรกs ciudades exactamente una vez y regrese a la ciudad de origen.

Para el grรกfico anterior, la ruta รณptima es 1-2-4-3-1. El costo del tour mรกs corto es 10 + 25 + 30 + 15 = 80.

Diferentes soluciones al problema del viajante

Diferentes soluciones al problema del viajante

El problema del viajante se clasifica como NP-difรญcil porque no existe ningรบn algoritmo conocido de tiempo polinomial que lo resuelva exactamente. La complejidad aumenta exponencialmente con el nรบmero de ciudades.

Existen mรบltiples formas de abordar el problema del viajante. Los enfoques mรกs comunes son:

Enfoque de fuerza bruta: El mรฉtodo ingenuo calcula todos los recorridos posibles y los compara. El nรบmero de recorridos en un grafo con n ciudades es n!, lo que hace que la fuerza bruta sea computacionalmente muy costosa para cualquier cosa que supere las diez ciudades aproximadamente.

Mรฉtodo de ramificaciรณn y acotaciรณn: El problema se divide en subproblemas, y las soluciones de estos subproblemas se combinan para formar una soluciรณn รณptima. Una poda eficaz descarta los recorridos parciales que no pueden superar el mejor coste actual.

Este tutorial demuestra la enfoque de programaciรณn dinรกmica, que es la versiรณn memorizada del mรฉtodo de ramificaciรณn y acotaciรณn y coincide con el algoritmo de Bellman-Held-Karp.

Programaciรณn dinรกmica: Este es un mรฉtodo exacto que busca la soluciรณn รณptima mediante la reutilizaciรณn de la superposiciรณn.ping resultados del subproblema. Es mรกs lento que el casi รณptimo. mรฉtodos codiciosospero siempre ofrece un recorrido รณptimo a nivel global.

La complejidad computacional de este enfoque es O(Nยฒ ร— 2^N), tema que abordaremos mรกs adelante en el artรญculo.

Mรฉtodo del vecino mรกs cercano: Un enfoque heurรญstico voraz que siempre salta a la ciudad no visitada mรกs cercana. Es mucho mรกs econรณmico que la programaciรณn dinรกmica, pero no garantiza un recorrido รณptimo, por lo que se utiliza para soluciones casi รณptimas cuando la velocidad es mรกs importante que la obtenciรณn de mรญnimos exactos.

Algoritmo para el problema del viajante

Utilizamos el enfoque de programaciรณn dinรกmica para resolver el problema del viajante. Antes de comenzar con el algoritmo, aclaremos algunos tรฉrminos:

  • Un grรกfico G = (V, E) es un conjunto de vรฉrtices y aristas.
  • V es el conjunto de vรฉrtices.
  • E es el conjunto de aristas.
  • Los vรฉrtices estรกn conectados a travรฉs de aristas.
  • Dist(i, j) denota la distancia no negativa entre los vรฉrtices i y j.

Supongamos que S es un subconjunto de ciudades extraรญdas de {1, 2, 3, โ€ฆ, n}, donde i y j son dos ciudades de ese subconjunto. Entonces cost(i, S, j) es la longitud del camino mรกs corto que comienza en i, visita cada ciudad en S exactamente una vez y termina en j.

Por ejemplo, cost(1, {2, 3, 4}, 1) denota el camino mรกs corto donde:

  • La ciudad inicial es 1.
  • Las ciudades 2, 3 y 4 se visitan solo una vez
  • El punto final es 1.

La recurrencia de la programaciรณn dinรกmica es:

  • Establecer cost(i, {}, i) = 0, lo que significa que comenzamos y terminamos en i con costo cero.
  • Cuando |S| > 1, definir cost(i, S, 1) = โˆž por la i โ‰  1, porque el coste real del tour aรบn se desconoce.
  • Comenzando en la ciudad 1, elige la siguiente ciudad de modo que cost(i, S, j) = min [ cost(i, S โˆ’ {i}, j) + dist(i, j) ] por la i โˆˆ S y i โ‰  j.

Para el grรกfico anterior, la matriz de adyacencia es la siguiente:

Algoritmo para el problema del viajante

distancia(i, j)1234
10101520
21003525
31535030
42025300

Asรญ es como procede el algoritmo:

Paso 1) El viaje comienza en la ciudad 1, visita todas las demรกs ciudades una vez y regresa a la ciudad 1.

Paso 2) S es un subconjunto de ciudades. Para cada |S| > 1, inicializar cost(i, S, 1) = โˆž. aquรญ cost(i, S, j) denota un recorrido que comienza en i, visita las ciudades en S una vez y llega a j. Comenzamos desde el infinito porque la distancia es desconocida en este punto. Por lo tanto, los valores son:

cost(2, {3, 4}, 1) = โˆž significa que comenzamos en la ciudad 2, pasamos por las ciudades 3 y 4, y llegamos a la 1, con un costo desconocido. De manera similar:

cost(3, {2, 4}, 1) = โˆž

cost(4, {2, 3}, 1) = โˆž

Paso 3) Para cada subconjunto de S, calcule:

cost(i, S, j) = min [ cost(i, S โˆ’ {i}, j) + dist(i, j) ], donde j โˆˆ S y i โ‰  j.

Ese es el recorrido de costo mรญnimo que comienza en i, visita el subconjunto de ciudades una vez y regresa a j. Debido a que el recorrido comienza en la ciudad 1, el costo รณptimo es cost(1, {other cities}, 1).

Trabajar la recurrencia paso a paso

Ahora S = {1, 2, 3, 4}. Hay cuatro elementos, por lo que el nรบmero de subconjuntos es 2^4 = 16Esos subconjuntos son:

1) |S| = 0: {ฮฆ}

2) |S| = 1: {{1}, {2}, {3}, {4}}

3) |S| = 2: {{1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 4}, {3, 4}}

4) |S| = 3: {{1, 2, 3}, {1, 2, 4}, {2, 3, 4}, {1, 3, 4}}

5) |S| = 4: {{1, 2, 3, 4}}

Dado que el recorrido comienza en la ciudad 1, podemos descartar cualquier subconjunto que contenga la ciudad 1 al calcular los costos intermedios.

El cรกlculo del algoritmo se desarrolla de la siguiente manera:

1) |S| =ฮฆ:

  • costo(2, ฮฆ, 1) = dist(2, 1) = 10
  • costo(3, ฮฆ, 1) = dist(3, 1) = 15
  • costo(4, ฮฆ, 1) = dist(4, 1) = 20

2) |S| = 1:

  • costo(2, {3}, 1) = dist(2, 3) + costo(3, ฮฆ, 1) = 35 + 15 = 50
  • costo(2, {4}, 1) = dist(2, 4) + costo(4, ฮฆ, 1) = 25 + 20 = 45
  • costo(3, {2}, 1) = dist(3, 2) + costo(2, ฮฆ, 1) = 35 + 10 = 45
  • costo(3, {4}, 1) = dist(3, 4) + costo(4, ฮฆ, 1) = 30 + 20 = 50
  • costo(4, {2}, 1) = dist(4, 2) + costo(2, ฮฆ, 1) = 25 + 10 = 35
  • costo(4, {3}, 1) = dist(4, 3) + costo(3, ฮฆ, 1) = 30 + 15 = 45

3) |S| = 2:

  • costo(2, {3, 4}, 1) = min [ dist(2, 3) + costo(3, {4}, 1) = 35 + 50 = 85, dist(2, 4) + costo(4, {3}, 1) = 25 + 45 = 70 ] = 70
  • costo(3, {2, 4}, 1) = min [ dist(3, 2) + costo(2, {4}, 1) = 35 + 45 = 80, dist(3, 4) + costo(4, {2}, 1) = 30 + 35 = 65 ] = 65
  • costo(4, {2, 3}, 1) = min [ dist(4, 2) + costo(2, {3}, 1) = 25 + 50 = 75, dist(4, 3) + costo(3, {2}, 1) = 30 + 45 = 75 ] = 75

4) |S| = 3:

  • costo(1, {2, 3, 4}, 1) = min [ dist(1, 2) + costo(2, {3, 4}, 1) = 10 + 70 = 80, dist(1, 3) + costo(3, {2, 4}, 1) = 15 + 65 = 80, dist(1, 4) + costo(4, {2, 3}, 1) = 20 + 75 = 95 ] = 80

Por lo tanto, la soluciรณn รณptima es 1-2-4-3-1.

Algoritmo para el problema del viajante

Pseudocรณdigo

Algorithm: Traveling-Salesman-Problem
Cost (1, {}, 1) = 0
for s = 2 to n do
    for all subsets S belongs to {1, 2, 3, ..., n} of size s
        Cost (s, S, 1) = Infinity
    for all i in S and i != 1
        Cost (i, S, j) = min {Cost (i, S - {i}, j) + dist(i, j) for j in S and i != j}
Return min(i) Cost (i, {1, 2, 3, ..., n}, j) + d(j, i)

Implantaciรณn en C/C++

Aquรญ estรก la implementaciรณn en C++La versiรณn que aparece a continuaciรณn corrige el error inicial del cรณdigo fuente. return error, que se producรญa despuรฉs de la primera permutaciรณn en lugar de enumerar todos los recorridos.

#include <bits/stdc++.h>
using namespace std;
#define V 4
#define MAX 1000000

int tsp(int graph[][V], int s) {
    vector<int> vertex;
    for (int i = 0; i < V; i++)
        if (i != s)
            vertex.push_back(i);

    int min_cost = MAX;
    do {
        int current_cost = 0;
        int j = s;
        for (int i = 0; i < vertex.size(); i++) {
            current_cost += graph[j][vertex[i]];
            j = vertex[i];
        }
        current_cost += graph[j][s];
        min_cost = min(min_cost, current_cost);
    } while (next_permutation(vertex.begin(), vertex.end()));

    return min_cost;
}

int main() {
    int graph[][V] = {
        { 0, 10, 15, 20 },
        { 10, 0, 35, 25 },
        { 15, 35, 0, 30 },
        { 20, 25, 30, 0 }
    };
    int s = 0;
    cout << tsp(graph, s) << endl;
    return 0;
}

Salida:

80

Implementaciรณn en Python

El Python La implementaciรณn refleja la C++ versiรณn. Corrige la fuente from itertools, import error tipogrรกfico de coma, el mal colocado return dentro del bucle interior y la hendidura suelta en s = 0.

from sys import maxsize
from itertools import permutations

V = 4

def tsp(graph, s):
    vertex = []
    for i in range(V):
        if i != s:
            vertex.append(i)

    min_cost = maxsize
    for perm in permutations(vertex):
        current_cost = 0
        k = s
        for j in perm:
            current_cost += graph[k][j]
            k = j
        current_cost += graph[k][s]
        min_cost = min(min_cost, current_cost)
    return min_cost

graph = [[0, 10, 15, 20],
         [10, 0, 35, 25],
         [15, 35, 0, 30],
         [20, 25, 30, 0]]
s = 0
print(tsp(graph, s))

Salida:

80

Soluciones acadรฉmicas para TSP

Los informรกticos han dedicado dรฉcadas a buscar algoritmos mejorados de tiempo polinomial para el problema del viajante. Hasta ahora, el problema del viajante sigue siendo NP-difรญcil.

Varias tรฉcnicas publicadas reducen la complejidad prรกctica para familias especรญficas de instancias del problema del viajante:

  • El TSP simรฉtrico clรกsico se resuelve mediante el Mรฉtodo del sufijo cero.
  • El Algoritmo de optimizaciรณn basado en biogeografรญa Utiliza estrategias de migraciรณn para resolver problemas de optimizaciรณn que se corresponden con el problema del viajante.
  • El Algoritmo evolutivo multiobjetivo Estรก diseรฑado para el problema del viajante multiobjetivo y se basa en NSGA-II.
  • El Sistema Multi-Agente Este enfoque resuelve el problema del viajante para N ciudades con recursos computacionales fijos.
  • El Heurรญstica de Lin-Kernighan y su sucesor LKH Ofrecer recorridos con una precisiรณn de entre el 2 % y el 3 % respecto al รณptimo en casos con millones de ciudades.
  • Concord Utiliza planos de corte y el mรฉtodo de ramificaciรณn y corte para calcular รณptimos exactos para instancias de referencia con decenas de miles de ciudades.

Aplicaciรณn del problema del viajante

El problema del viajante se presenta en el mundo real tanto en su forma pura como modificada. Algunas de sus principales aplicaciones son:

  • Planificaciรณn, logรญstica y fabricaciรณn de microchips: Los problemas de inserciรณn de chips en la industria de los microchips se modelan como variantes del problema del viajante para minimizar el tiempo de desplazamiento del brazo robรณtico.
  • Secuencia ADN: En la secuenciaciรณn de ADN se utiliza un TSP modificado, donde las ciudades representan fragmentos de ADN y las distancias representan la similitud entre los fragmentos.
  • Astronomรญa: Los astrรณnomos utilizan TSP para minimizar el tiempo que se tarda en mover los telescopios entre los objetivos de observaciรณn.
  • Control รณptimo: Las formulaciones del problema del viajante modelan problemas de control รณptimo en los que se deben respetar mรบltiples restricciones al tiempo que se minimiza el coste de recorrido.
  • Entrega de รบltima milla: AmazonLas aplicaciones de reparto de comida, como UPS, resuelven variantes dinรกmicas del problema del viajante para secuenciar las paradas de los conductores.
  • Picking en almacรฉn: Los operarios, tanto robรณticos como humanos, siguen rutas optimizadas mediante el algoritmo TSP que reducen el tiempo de desplazamiento dentro de los centros de distribuciรณn.

Anรกlisis de complejidad de TSP

  • Complejidad del tiempo: El enfoque de programaciรณn dinรกmica de Held-Karp resuelve 2N subconjuntos para cada nodo inicial, dando N ร— 2^N subproblemas. Cada subproblema requiere un tiempo lineal para combinarse. Si el nodo de origen no estรก especificado, se requiere un bucle externo sobre N nodos. La complejidad temporal total es O(Nยฒ ร— 2^N).
  • Complejidad espacial: La tabla DP almacena C(S, i) para cada subconjunto S del conjunto de vรฉrtices. Hay 2N subconjuntos por nodo, por lo que la complejidad espacial es O(N ร— 2^N), que a menudo se escribe como O(2^N) cuando N se considera fijo.

A continuaciรณn, aprenda sobre el Algoritmo del tamiz de Eratรณstenes.

Preguntas Frecuentes

El problema del viajante consiste en encontrar la ruta mรกs corta que comience en una ciudad elegida, visite todas las demรกs ciudades exactamente una vez y regrese al punto de partida. Es un problema de optimizaciรณn NP-difรญcil de referencia en informรกtica.

El problema del viajante es NP-difรญcil porque no se conoce ningรบn algoritmo de tiempo polinomial que resuelva cada instancia de forma exacta. La fuerza bruta se ejecuta en tiempo O(n!), y el mejor enfoque exacto de programaciรณn dinรกmica aรบn requiere tiempo O(Nยฒ ยท 2^N), que crece exponencialmente.

La programaciรณn dinรกmica almacena en cachรฉ las rutas mรกs cortas a travรฉs de cada subconjunto de ciudades. El costo de recurrencia de Held-Karp (i, S, j) reutiliza subproblemas mรกs pequeรฑos para construir el recorrido รณptimo, reduciendo el costo de fuerza bruta de O(n!) a O(Nยฒ ยท 2^N).

Las variantes del problema del viajante (TSP) impulsan el enrutamiento de entregas de รบltima milla, las rutas de recolecciรณn en almacenes, la perforaciรณn de placas de circuito impreso, la secuenciaciรณn de ADN, la programaciรณn de telescopios y la planificaciรณn de carga de camiones. Cualquier tarea que visite un conjunto fijo de paradas y regrese a la base es candidata a ser un problema del viajante.

La fuerza bruta prueba cada permutaciรณn de ciudades y siempre devuelve el รณptimo exacto con un coste de O(n!). El vecino mรกs cercano salta codiciosamente a la ciudad no visitada mรกs cercana en un tiempo de O(nยฒ), lo que proporciona un recorrido rรกpido pero subรณptimo, tรญpicamente un 25 % superior al รณptimo.

Lin-Kernighan, LKH, Christofides, recocido simulado, optimizaciรณn por colonia de hormigas y algoritmos genรฉticos ofrecen recorridos casi รณptimos para instancias grandes del problema del viajante. Concorde resuelve el problema del viajante de forma exacta para datos de referencia con decenas de miles de ciudades.

Las redes neuronales grรกficas y los agentes de aprendizaje por refuerzo, como las redes de punteros, aprenden heurรญsticas que generan rutas competitivas para el problema del viajante. Destacan en tareas estructuradas de planificaciรณn de rutas, como la logรญstica y la entrega de mercancรญas.

Sรญ. GitHub Copilot y asistentes de IA similares proporcionan soluciones TSP en C++, Python, o Java, sugieren la memorizaciรณn de Held-Karp y generan heurรญsticas como el vecino mรกs cercano o 2-opt para la evaluaciรณn comparativa.

Resumir este post con: