Problema del commesso viaggiatore: Python, C++ Algoritmo

โšก Riepilogo intelligente

Il problema del commesso viaggiatore รจ un classico problema di ottimizzazione NP-difficile che richiede di trovare il percorso piรน breve che visiti ogni cittร  esattamente una volta e ritorni al punto di partenza, utilizzando i dati di distanza forniti tramite un grafo.

  • ๏ธ Dichiarazione problema: Dato un grafo pesato di cittร  e distanze a coppie, trovare il ciclo hamiltoniano a costo minimo che inizia e termina nella stessa cittร  di origine.
  • โš™๏ธ Famiglie di soluzioni: La forza bruta enumera tutti gli n! percorsi, la ricerca branch-and-bound pota i sottoproblemi, la programmazione dinamica memorizza nella cache i sottoproblemi e il vicino piรน prossimo offre un'euristica veloce.
  • ๐Ÿ“‰ Programmazione dinamica: Il costo di ricorrenza di Held-Karp(i, S, j) riutilizza i percorsi piรน brevi attraverso i sottoinsiemi di vertici e fornisce una soluzione esatta con complessitร  temporale O(Nยฒ ยท 2^N).
  • ๐Ÿ’ป Code Esempi: Il tutorial funziona perfettamente C++ and Python implementazioni che calcolano il costo ottimale del tour per una matrice di adiacenza di quattro cittร .
  • ๐ŸŒ applicazioni: Le varianti di TSP includono l'ottimizzazione del percorso di alimentazione, la foratura di PCB, il sequenziamento del DNA, la pianificazione del telescopio e la pianificazione del percorso di prelievo in magazzino.
  • ๐Ÿค– Angolo dell'IA: Le moderne tecniche di apprendimento per rinforzo, le reti neurali a grafo e le euristiche come Lin-Kernighan e Concorde risolvono istanze di TSP (Total Selling Probability) su larga scala utilizzate nella logistica.

Problema del commesso viaggiatore

Cosโ€™รจ il problema del commesso viaggiatore (TSP)?

Il problema del commesso viaggiatore (TSP) รจ un classico problema di ottimizzazione combinatoria nell'informatica teorica. Dato un grafo di cittร , il TSP richiede di trovare il percorso piรน breve che visiti ogni nodo esattamente una volta e ritorni alla cittร  di partenza.

Il problema viene descritto fornendo un elenco di cittร  e le distanze tra ciascuna coppia di cittร .

Obiettivo: Parti dalla cittร  di origine, visita ogni altra cittร  esattamente una volta e torna alla cittร  di partenza. L'obiettivo รจ trovare il percorso di andata e ritorno piรน breve possibile.

Esempio di TSP

Si consideri il grafico sottostante, dove 1, 2, 3 e 4 rappresentano le cittร  e il peso di ciascun arco rappresenta la distanza tra tali cittร .

Esempio di TSP

L'obiettivo รจ trovare il tour piรน breve possibile che parta dalla cittร  di origine, visiti ogni altra cittร  esattamente una volta e ritorni alla cittร  di origine.

Per il grafico sopra, il percorso ottimale รจ 1-2-4-3-1. Il costo del tour piรน breve รจ 10 + 25 + 30 + 15 = 80.

Soluzioni diverse al problema del commesso viaggiatore

Soluzioni diverse al problema del commesso viaggiatore

Il problema del commesso viaggiatore รจ classificato come NP-difficile perchรฉ nessun algoritmo noto in tempo polinomiale รจ in grado di risolverlo esattamente. La complessitร  cresce esponenzialmente con il numero di cittร .

Esistono diversi modi per attaccare il TSP. Gli approcci piรน comuni sono:

Approccio basato sulla forza bruta: Il metodo ingenuo calcola ogni possibile percorso e li confronta. Il numero di percorsi in un grafo con n cittร  รจ n!, il che rende il calcolo con la forza bruta estremamente oneroso per qualsiasi cosa che vada oltre una decina di cittร .

Metodo Branch and Bound: Il problema viene scomposto in sottoproblemi e le soluzioni di questi sottoproblemi si combinano per formare una soluzione ottimale. Un'efficace potatura scarta i percorsi parziali che non riescono a battere il miglior costo corrente.

Questo tutorial dimostra che approccio di programmazione dinamica, che รจ la versione con memorizzazione dei risultati dell'algoritmo branch and bound e corrisponde all'algoritmo Bellman-Held-Karp.

Programmazione dinamica: Si tratta di un metodo esatto che cerca la soluzione ottimale riutilizzando la sovrapposizioneping risultati del sottoproblema. รˆ piรน lento rispetto a quello quasi ottimale metodi avidima restituisce sempre un percorso globalmente ottimale.

La complessitร  computazionale di questo approccio รจ O(Nยฒ ร— 2^N), argomento che approfondiremo piรน avanti nell'articolo.

Metodo del vicino piรน prossimo: Un approccio euristico greedy che sceglie sempre la cittร  non ancora visitata piรน vicina. รˆ molto piรน economico della programmazione dinamica, ma non garantisce un percorso ottimale, quindi viene utilizzato per soluzioni quasi ottimali quando la velocitร  รจ piรน importante del minimo esatto.

Algoritmo per il problema del commesso viaggiatore

Utilizziamo l'approccio della programmazione dinamica per risolvere il problema del commesso viaggiatore (TSP). Prima di iniziare l'algoritmo, chiariamo alcuni termini:

  • Un grafico G = (V, E) รจ un insieme di vertici e spigoli.
  • V รจ l'insieme dei vertici.
  • E รจ l'insieme degli spigoli.
  • I vertici sono collegati tramite bordi.
  • Dist(i, j) indica la distanza non negativa tra i vertici i e j.

Supponiamo che S sia un sottoinsieme di cittร  tratte da {1, 2, 3, โ€ฆ, n} dove i e j sono due cittร  in quel sottoinsieme. Allora cost(i, S, j) รจ la lunghezza del percorso piรน breve che parte da i, visita ogni cittร  di S esattamente una volta e termina in j.

Per esempio, cost(1, {2, 3, 4}, 1) indica il percorso piรน breve dove:

  • La cittร  di partenza รจ 1
  • Le cittร  2, 3 e 4 vengono visitate una sola volta
  • Il punto finale รจ 1

La ricorrenza della programmazione dinamica รจ:

  • Impostato cost(i, {}, i) = 0, il che significa che partiamo e finiamo in i con costo zero.
  • Quando |S| > 1, definire cost(i, S, 1) = โˆž per i โ‰  1, perchรฉ il costo effettivo del tour non รจ ancora noto.
  • Partendo dalla cittร  1, scegli la cittร  successiva in modo che cost(i, S, j) = min [ cost(i, S โˆ’ {i}, j) + dist(i, j) ] per i โˆˆ S and i โ‰  j.

Per il grafico sopra riportato, la matrice di adiacenza รจ la seguente:

Algoritmo per il problema del commesso viaggiatore

dist(i, j)1234
10101520
21003525
31535030
42025300

Ecco come procede l'algoritmo:

Passo 1) Il viaggio inizia nella cittร  1, visita ogni altra cittร  una volta e ritorna alla cittร  1.

Passo 2) S รจ un sottoinsieme di cittร . Per ogni |S| > 1, inizializza cost(i, S, 1) = โˆž. Qui cost(i, S, j) indica un tour che inizia da i, visita le cittร  in S una volta e raggiunge j. Partiamo dall'infinito perchรฉ la distanza รจ sconosciuta in questo punto. Quindi i valori sono:

cost(2, {3, 4}, 1) = โˆž significa che partiamo dalla cittร  2, attraversiamo le cittร  3 e 4 e raggiungiamo la 1, con un costo sconosciuto. Allo stesso modo:

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

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

Passo 3) Per ogni sottoinsieme di S, calcola:

cost(i, S, j) = min [ cost(i, S โˆ’ {i}, j) + dist(i, j) ]Durante la serata, j โˆˆ S and i โ‰  j.

Questo รจ il tour di costo minimo che inizia da i, visita il sottoinsieme di cittร  una volta e ritorna a j. Poichรฉ il tour inizia dalla cittร  1, il costo ottimale รจ cost(1, {other cities}, 1).

Analisi della ricorrenza passo dopo passo

Ora S = {1, 2, 3, 4}. Ci sono quattro elementi, quindi il numero di sottoinsiemi รจ 2^4 = 16Questi sottoinsiemi sono:

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}}

Poichรฉ il tour inizia dalla cittร  1, possiamo scartare ogni sottoinsieme che contiene la cittร  1 durante il calcolo dei costi intermedi.

Il calcolo dell'algoritmo si svolge come segue:

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

Quindi la soluzione ottimale รจ 1-2-4-3-1.

Algoritmo per il problema del commesso viaggiatore

Pseudo-codice

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)

Implementazione in C/C++

Ecco l'implementazione in C++. La versione seguente corregge l'inizio della sorgente return bug, che ritornava dopo la primissima permutazione invece di enumerare tutti i tour.

#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;
}

Produzione:

80

implementazione in Python

Migliori Python l'implementazione rispecchia l' C++ versione. Corregge la sorgente from itertools, import errore di battitura, la virgola fuori posto return all'interno del circuito interno e l'incavo fuori posto su 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))

Produzione:

80

Soluzioni accademiche al TSP

Gli informatici hanno trascorso decenni alla ricerca di algoritmi a tempo polinomiale migliorati per il problema del commesso viaggiatore. Finora, il problema del commesso viaggiatore rimane NP-difficile.

Diverse tecniche pubblicate riducono la complessitร  pratica per specifiche famiglie di istanze del problema del commesso viaggiatore (TSP):

  • Il TSP simmetrico classico viene risolto dal Metodo del suffisso zero.
  • Migliori Algoritmo di ottimizzazione basato sulla biogeografia Utilizza strategie di migrazione per risolvere problemi di ottimizzazione che corrispondono al problema del commesso viaggiatore (TSP).
  • Migliori Algoritmo evolutivo multi-obiettivo รˆ progettato per il problema del commesso viaggiatore multi-obiettivo e si basa su NSGA-II.
  • Migliori Sistema multi-agente Questo approccio risolve il problema del commesso viaggiatore (TSP) per N cittร  con risorse computazionali fisse.
  • Migliori Euristica di Lin-Kernighan e il suo successore LKH fornire tour con una precisione del 2-3% rispetto all'ottimale per casi con milioni di cittร .
  • Concordia Utilizza piani di taglio e l'algoritmo branch-and-cut per calcolare gli ottimi esatti per istanze di benchmark con decine di migliaia di cittร .

Applicazione del problema del commesso viaggiatore

Il problema del commesso viaggiatore si presenta nel mondo reale sia in forma pura che modificata. Alcune delle principali applicazioni sono:

  • Pianificazione, logistica e produzione di microchip: I problemi di inserimento dei chip nell'industria dei microchip vengono modellati come varianti del problema del commesso viaggiatore (TSP) per minimizzare il tempo di spostamento del braccio robotico.
  • Sequenziamento del DNA: Un TSP modificato viene utilizzato nel sequenziamento del DNA, dove le cittร  rappresentano i frammenti di DNA e le distanze rappresentano la similaritร  tra i frammenti.
  • Astronomia: Gli astronomi utilizzano il TSP per ridurre al minimo il tempo impiegato per spostare i telescopi tra gli obiettivi di osservazione.
  • Controllo ottimale: Le formulazioni TSP modellano problemi di controllo ottimo in cui รจ necessario rispettare molteplici vincoli, minimizzando al contempo il costo di attraversamento.
  • Consegna dell'ultimo miglio: AmazonUPS e le app di consegna di cibo risolvono le varianti dinamiche del TSP per sequenziare le fermate per gli autisti.
  • Prelievo in magazzino: Robot e operatori umani seguono percorsi ottimizzati dal sistema TSP (Total Service Provider) che riducono i tempi di spostamento all'interno dei centri di distribuzione.

Analisi della complessitร  del TSP

  • Complessitร  temporale: L'approccio di programmazione dinamica Held-Karp risolve 2N sottoinsiemi per ogni nodo iniziale, dando N ร— 2^N sottoproblemi. Ogni sottoproblema richiede un tempo lineare per essere combinato. Se il nodo di origine non รจ specificato, รจ necessario un ciclo esterno su N nodi. La complessitร  temporale totale รจ O(Nยฒ ร— 2^N).
  • Complessitร  spaziale: La tabella DP memorizza C(S, i) per ogni sottoinsieme S dell'insieme dei vertici. Ce ne sono 2N sottoinsiemi per nodo, quindi la complessitร  dello spazio รจ O(N ร— 2^N), che spesso viene scritto come O(2^N) quando N viene considerato fisso.

Successivamente, scopri il Algoritmo del crivello di Eratostene.

DOMANDE FREQUENTI

Il problema del commesso viaggiatore consiste nel trovare il percorso piรน breve che parta da una cittร  prescelta, visiti ogni altra cittร  esattamente una volta e ritorni al punto di partenza. Si tratta di un problema di ottimizzazione NP-difficile di riferimento nell'informatica.

Il problema del commesso viaggiatore (TSP) รจ NP-difficile perchรฉ non รจ noto alcun algoritmo in tempo polinomiale in grado di risolvere ogni istanza in modo esatto. La forza bruta ha una complessitร  temporale di O(n!), e il miglior approccio di programmazione dinamica esatta richiede comunque un tempo di O(Nยฒ ยท 2^N), che cresce esponenzialmente.

La programmazione dinamica memorizza nella cache i percorsi piรน brevi attraverso ogni sottoinsieme di cittร . Il costo di ricorrenza di Held-Karp(i, S, j) riutilizza sottoproblemi piรน piccoli per costruire il percorso ottimale, riducendo il costo della forza bruta da O(n!) a O(Nยฒ ยท 2^N).

Le varianti del TSP (Task Service Provider) alimentano la pianificazione dei percorsi di consegna dell'ultimo miglio, i percorsi di prelievo in magazzino, la foratura di PCB, il sequenziamento del DNA, la programmazione dei telescopi e la pianificazione del carico dei camion. Qualsiasi attivitร  che visiti una serie fissa di fermate e ritorni alla base รจ un potenziale candidato TSP.

Il metodo della forza bruta testa ogni permutazione di cittร  e restituisce sempre l'ottimo esatto con un costo di O(n!). Il vicino piรน prossimo salta avidamente alla cittร  non visitata piรน vicina in tempo O(nยฒ), fornendo un tour veloce ma subottimale, tipicamente il 25% al โ€‹โ€‹di sopra dell'ottimo.

Gli algoritmi Lin-Kernighan, LKH, Christofides, il ricottura simulata, l'ottimizzazione tramite colonie di formiche e gli algoritmi genetici forniscono percorsi quasi ottimali per istanze di TSP di grandi dimensioni. Concorde risolve il TSP esatto per input di benchmark con decine di migliaia di cittร .

Le reti neurali a grafo e gli agenti di apprendimento per rinforzo, come le reti a puntatore, apprendono euristiche che producono percorsi TSP competitivi. Eccellono in compiti di pianificazione di percorsi strutturati come la consegna e la logistica.

Sรฌ. GitHub Copilot e assistenti IA simili creano soluzioni TSP in C++, Python, o Javasuggeriscono la memorizzazione tramite Held-Karp e generano euristiche come il vicino piรน prossimo o 2-opt per il benchmarking.

Riassumi questo post con: