Problem komiwojażera: Python, C++ Algorytm

⚡ Inteligentne podsumowanie

Problem komiwojażera to klasyczne zadanie optymalizacyjne NP-trudne, które polega na znalezieniu najkrótszej trasy, która obejmuje każde miasto dokładnie raz i powraca do początku układu współrzędnych, wykorzystując dane o odległościach przedstawione na wykresie.

  • 🗺️. Przedstawienie problemu: Mając ważony graf miast i odległości parami, znajdź cykl Hamiltona o minimalnym koszcie, który zaczyna się i kończy w tym samym mieście początkowym.
  • ⚙️ Rodziny rozwiązań: Metoda siłowa wylicza wszystkie n! tras, metoda rozgałęzień i ograniczeń przeszukuje, programowanie dynamiczne buforuje podproblemy, a metoda najbliższego sąsiada oferuje szybką heurystykę.
  • 📉 Programowanie dynamiczne: Rekurencja Helda-Karpa cost(i, S, j) wykorzystuje ponownie najkrótsze ścieżki przez podzbiory wierzchołków i zapewnia dokładne rozwiązanie w czasie O(N² · 2^N).
  • 💻 Code Przykłady: Samouczek jest w pełni sprawny C++ oraz Python implementacje obliczające optymalny koszt wycieczki dla macierzy sąsiedztwa czterech miast.
  • 🌍 Aplikacje: Warianty TSP zapewniają optymalizację tras dostaw, wiercenie płytek PCB, sekwencjonowanie DNA, harmonogramowanie teleskopów i planowanie ścieżek kompletacji w magazynie.
  • 🤖 Kąt AI: Nowoczesne uczenie maszynowe, sieci neuronowe grafowe i heurystyki, takie jak Lin-Kernighan i Concorde, rozwiązują problemy związane z dużą liczbą wystąpień TSP wykorzystywanych w logistyce.

Problem sprzedawcy podróży

Na czym polega problem komiwojażera (TSP)?

Problem komiwojażera (TSP) to klasyczny problem optymalizacji kombinatorycznej w informatyce teoretycznej. Biorąc pod uwagę graf miast, TSP pyta o najkrótszą ścieżkę, która przechodzi przez każdy węzeł dokładnie raz i wraca do miasta początkowego.

W opisie problemu podano listę miast i odległości między każdą parą miast.

Cel: Rozpocznij w mieście początkowym, odwiedź każde inne miasto dokładnie raz i wróć do miasta początkowego. Celem jest znalezienie najkrótszej możliwej trasy w obie strony.

Przykład TSP

Rozważ poniższy wykres, gdzie 1, 2, 3 i 4 reprezentują miasta, a waga na każdej krawędzi przedstawia odległość między tymi miastami.

Przykład TSP

Celem jest znalezienie najkrótszej możliwej trasy, która zaczyna się w mieście początkowym, odwiedza każde inne miasto dokładnie raz i wraca do miasta początkowego.

W przypadku powyższego wykresu optymalna trasa to 1-2-4-3-1Koszt najkrótszej wycieczki wynosi 10 + 25 + 30 + 15 = 80.

Różne rozwiązania problemu komiwojażera

Różne rozwiązania problemu komiwojażera

Problem komiwojażera jest klasyfikowany jako NP-trudny, ponieważ żaden znany algorytm działający w czasie wielomianowym nie rozwiązuje go dokładnie. Złożoność rośnie wykładniczo wraz z liczbą miast.

Istnieje wiele sposobów atakowania TSP. Najpopularniejsze podejścia to:

Podejście siłowe: Metoda naiwna oblicza każdą możliwą trasę i porównuje je. Liczba tras na wykresie z n miastami wynosi n!, co sprawia, że ​​siłowe podejście jest bardzo kosztowne obliczeniowo w przypadku miast większych niż dziesięć.

Metoda rozgałęzienia i ograniczenia: Problem jest dzielony na podproblemy, a rozwiązania tych podproblemów łączą się w rozwiązanie optymalne. Skuteczne przycinanie eliminuje częściowe trasy, które nie mogą przebić aktualnego najlepszego kosztu.

Ten samouczek pokazuje podejście programowania dynamicznego, która jest zapamiętaną wersją algorytmu rozgałęzienia i ograniczenia i odpowiada algorytmowi Bellmana-Helda-Karpa.

Programowanie dynamiczne: Jest to dokładna metoda, która polega na poszukiwaniu optymalnego rozwiązania poprzez ponowne wykorzystanie nakładających się elementówping wyniki podproblemu. Jest wolniejszy niż rozwiązanie bliskie optymalnemu chciwe metody, ale zawsze zwraca globalnie optymalną trasę.

Złożoność obliczeniowa tego podejścia wynosi O(N² × 2^N), o czym porozmawiamy później w artykule.

Metoda najbliższego sąsiada: Heurystyczne, zachłanne podejście, które zawsze przeskakuje do najbliższego, nieodwiedzonego miasta. Jest znacznie tańsze niż programowanie dynamiczne, ale nie gwarantuje optymalnej trasy, dlatego stosuje się je w przypadku rozwiązań niemal optymalnych, gdy szybkość ma większe znaczenie niż dokładne minima.

Algorytm problemu komiwojażera

Do rozwiązania problemu TSP stosujemy podejście programowania dynamicznego. Zanim rozpoczniemy algorytm, ustalmy kilka terminologii:

  • Wykres G = (V, E) jest zbiorem wierzchołków i krawędzi.
  • V jest zbiorem wierzchołków.
  • E jest zbiorem krawędzi.
  • Wierzchołki są połączone krawędziami.
  • Dist(i, j) oznacza nieujemną odległość między wierzchołkami i oraz j.

Załóżmy, że S jest podzbiorem miast wybranych z {1, 2, 3, …, n}, gdzie i oraz j to dwa miasta w tym podzbiorze. Wówczas cost(i, S, j) jest długością najkrótszej ścieżki, która zaczyna się w i, przechodzi przez każde miasto w S dokładnie raz i kończy się w j.

Na przykład, cost(1, {2, 3, 4}, 1) oznacza najkrótszą ścieżkę, gdzie:

  • Miasto początkowe to 1
  • Miasta 2, 3 i 4 odwiedza się tylko raz
  • Punktem końcowym jest 1

Rekurencja programowania dynamicznego jest następująca:

  • Zestaw cost(i, {}, i) = 0, co oznacza, że ​​zaczynamy i kończymy na i przy zerowym koszcie.
  • Kiedy |S| > 1, zdefiniować cost(i, S, 1) = ∞ dla i ≠ 1, ponieważ prawdziwy koszt wycieczki nie jest jeszcze znany.
  • Zaczynając od miasta 1, wybierz kolejne miasto, aby cost(i, S, j) = min [ cost(i, S − {i}, j) + dist(i, j) ] dla i ∈ S oraz i ≠ j.

W przypadku powyższego grafu macierz sąsiedztwa wygląda następująco:

Algorytm problemu komiwojażera

dist(i, j)1234
10101520
21003525
31535030
42025300

Oto jak działa algorytm:

Krok 1) Podróż rozpoczyna się w mieście 1, odwiedza jednokrotnie każde inne miasto i wraca do miasta 1.

Krok 2) S to podzbiór miast. Dla każdego |S| > 1 zainicjuj cost(i, S, 1) = ∞. Tutaj cost(i, S, j) Oznacza trasę rozpoczynającą się w punkcie i, odwiedzającą miasta w S raz i docierającą do punktu j. Zaczynamy od nieskończoności, ponieważ odległość w tym punkcie jest nieznana. Wartości są zatem następujące:

cost(2, {3, 4}, 1) = ∞ Oznacza to, że zaczynamy w mieście 2, przechodzimy przez miasta 3 i 4 i docieramy do miasta 1, z nieznanym kosztem. Analogicznie:

cost(3, {2, 4}, 1) = ∞

cost(4, {2, 3}, 1) = ∞

Krok 3) Dla każdego podzbioru S oblicz:

cost(i, S, j) = min [ cost(i, S − {i}, j) + dist(i, j) ], Gdzie j ∈ S oraz i ≠ j.

To jest wycieczka o minimalnym koszcie, która rozpoczyna się w i, odwiedza podzbiór miast raz i wraca do j. Ponieważ wycieczka rozpoczyna się w mieście 1, optymalny koszt wynosi cost(1, {other cities}, 1).

Praca z rekurencją krok po kroku

Teraz S = {1, 2, 3, 4}. Elementy są cztery, więc liczba podzbiorów wynosi 2^4 = 16Te podzbiory to:

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

Ponieważ wycieczka rozpoczyna się w mieście 1, możemy pominąć każdy podzbiór zawierający miasto 1 podczas obliczania kosztów pośrednich.

Obliczenia algorytmu przebiegają następująco:

1) |S| = Φ:

  • koszt(2, Φ, 1) = dystrybucja(2, 1) = 10
  • koszt(3, Φ, 1) = dystrybucja(3, 1) = 15
  • koszt(4, Φ, 1) = dystrybucja(4, 1) = 20

2) |S| = 1:

  • koszt(2, {3}, 1) = dyst(2, 3) + koszt(3, Φ, 1) = 35 + 15 = 50
  • koszt(2, {4}, 1) = dyst(2, 4) + koszt(4, Φ, 1) = 25 + 20 = 45
  • koszt(3, {2}, 1) = dyst(3, 2) + koszt(2, Φ, 1) = 35 + 10 = 45
  • koszt(3, {4}, 1) = dyst(3, 4) + koszt(4, Φ, 1) = 30 + 20 = 50
  • koszt(4, {2}, 1) = dyst(4, 2) + koszt(2, Φ, 1) = 25 + 10 = 35
  • koszt(4, {3}, 1) = dyst(4, 3) + koszt(3, Φ, 1) = 30 + 15 = 45

3) |S| = 2:

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

4) |S| = 3:

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

Tak więc optymalnym rozwiązaniem jest 1-2-4-3-1.

Algorytm problemu komiwojażera

Pseudo kod

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)

Implementacja w C/C++

Oto implementacja w C++Wersja poniżej naprawia wczesną wersję źródła return błąd, który powrócił po pierwszej permutacji zamiast wymienić wszystkie trasy.

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

Wyjście:

80

wdrożenie w Python

Python wdrożenie odzwierciedla C++ wersja. Poprawia źródło from itertools, import błąd literowy, przecinek źle umieszczony return wewnątrz pętli wewnętrznej i przypadkowe wcięcie na 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))

Wyjście:

80

Rozwiązania akademickie dla TSP

Informatycy od dziesięcioleci poszukują ulepszonych algorytmów wielomianowych dla problemu komiwojażera. Jak dotąd TSP pozostaje problemem NP-trudnym.

Kilka opublikowanych technik redukuje praktyczną złożoność dla określonych rodzin instancji TSP:

  • Klasyczny symetryczny TSP jest rozwiązywany przez Metoda sufiksu zerowego.
  • Algorytm optymalizacyjny oparty na biogeografii stosuje strategie migracji w celu rozwiązania problemów optymalizacyjnych mapowanych na TSP.
  • Wielokryterialny algorytm ewolucyjny jest przeznaczony do wielokryterialnego TSP i bazuje na NSGA-II.
  • System wieloagentowy Podejście to rozwiązuje problem TSP dla N miast ze stałymi zasobami obliczeniowymi.
  • Heurystyka Lin-Kernighana i jego następca LKH dostarczać wycieczki w zakresie 2-3% od wartości optymalnej w przypadku milionów miast.
  • Zgoda wykorzystuje płaszczyzny cięcia i metodę „rozgałęziaj i tnij”, aby obliczyć dokładne wartości optymalne dla przypadków testowych obejmujących dziesiątki tysięcy miast.

Zastosowanie problemu komiwojażera

Problem komiwojażera pojawia się w świecie rzeczywistym zarówno w czystej, jak i zmodyfikowanej formie. Oto niektóre z jego głównych zastosowań:

  • Planowanie, logistyka i produkcja mikroprocesorów: Problemy z wkładaniem układów scalonych w przemyśle mikroprocesorowym są modelowane jako warianty TSP w celu zminimalizowania czasu ruchu ramienia robota.
  • Sekwencjonowanie DNA: Zmodyfikowana metoda TSP jest wykorzystywana w sekwencjonowaniu DNA, w którym miasta reprezentują fragmenty DNA, a odległości reprezentują podobieństwo między fragmentami.
  • Astronomia: Astronomowie wykorzystują technologię TSP, aby zminimalizować czas potrzebny na obracanie teleskopów między celami obserwacji.
  • Optymalna kontrola: Formuły TSP modelują problemy optymalnego sterowania, w których konieczne jest przestrzeganie wielu ograniczeń przy jednoczesnym minimalizowaniu kosztów przechodzenia.
  • Dostawa ostatniej mili: Amazon, UPS i aplikacje do dostawy żywności rozwiązują dynamiczne warianty TSP w celu ustalenia kolejności postojów kierowców.
  • Kompletacja magazynowa: Roboty i ludzie kompletujący zamówienia poruszają się po trasach zoptymalizowanych pod kątem TSP, co skraca czas przemieszczania się wewnątrz centrów dystrybucji.

Analiza złożoności TSP

  • Złożoność czasowa: Podejście programowania dynamicznego Helda-Karpa rozwiązuje 2N podzbiory dla każdego węzła początkowego, dając N × 2^N Podproblemy. Połączenie każdego podproblemu zajmuje czas liniowy. Jeśli węzeł początkowy jest nieokreślony, wymagana jest zewnętrzna pętla obejmująca N węzłów. Całkowita złożoność czasowa wynosi O(N² × 2^N).
  • Złożoność przestrzeni: Tabela DP przechowuje C(S, i) dla każdego podzbioru S zbioru wierzchołków. Istnieją 2N podzbiorów na węzeł, więc złożoność przestrzenna wynosi O(N × 2^N), które często zapisuje się jako O(2^N) gdy N traktujemy jako stałe.

Następnie dowiedz się więcej o Algorytm sita Eratostenesa.

FAQ

Problem komiwojażera polega na znalezieniu najkrótszej trasy, która rozpoczyna się w wybranym mieście, odwiedza każde inne miasto dokładnie raz i wraca do początku układu współrzędnych. Jest to wzorcowy problem optymalizacyjny NP-trudny w informatyce.

TSP jest NP-trudny, ponieważ żaden algorytm wielomianowy nie jest znany i nie rozwiązuje dokładnie każdego przypadku. Siłowe rozwiązanie problemu zajmuje O(n!), a najlepsze podejście do dokładnego programowania dynamicznego nadal wymaga czasu O(N² · 2^N), który rośnie wykładniczo.

Programowanie dynamiczne buforuje najkrótsze ścieżki w każdym podzbiorze miast. Metoda rekurencyjna Helda-Karpa cost(i, S, j) wykorzystuje mniejsze podproblemy do zbudowania optymalnej trasy, zmniejszając koszt obliczeń siłowych z O(n!) do O(N² · 2^N).

Warianty TSP wspomagają planowanie dostaw na ostatniej mili, ścieżek kompletacji zamówień w magazynach, wiercenia PCB, sekwencjonowanie DNA, planowanie pracy teleskopów i planowanie załadunku ciężarówek. Każde zadanie, które obejmuje stały zestaw przystanków i powrót do bazy, jest kandydatem do TSP.

Test siłowy sprawdza każdą permutację miast i zawsze zwraca dokładne optimum kosztem O(n!). Najbliższy sąsiad łapczywie przeskakuje do najbliższego nieodwiedzonego miasta w czasie O(n²), co zapewnia szybką, ale suboptymalną trasę, zazwyczaj o 25% wyższą od optimum.

Lin-Kernighan, LKH, Christofides, symulowane wyżarzanie, optymalizacja kolonii mrówek i algorytmy genetyczne zapewniają niemal optymalne trasy dla dużych instancji TSP. Concorde rozwiązuje dokładne obliczenia TSP dla danych testowych z dziesiątkami tysięcy miast.

Graficzne sieci neuronowe i agenci uczenia się przez wzmacnianie, tacy jak sieci wskaźników, uczą się heurystyk, które generują konkurencyjne trasy TSP. Doskonale radzą sobie ze strukturalnym planowaniem tras, takim jak dostawy i logistyka.

Tak. GitHub Copilot i podobne rozwiązania AI wspierają rozwiązania TSP C++, Pythonlub Java, sugerują zapamiętywanie Held-Karp i generują heurystyki, takie jak najbliższy sąsiad lub 2-opt do celów benchmarkingu.

Podsumuj ten post następująco: