Erweitert

Das Travelling-Salesman-Problem (TSP)

oben weiter

.
Algorithmus:

TSP(StartIndex, EndIndex: IndexType; var Distance :Real; AriMax: Integer);
var      Schranke, AktWegLaenge    :Real;
         WegeMax                   :IndexType;
type     TMinEntfs = array[KnotenAnzahl] of IndexType;
var      MinWeg                    :TMinEntf;

 Besuche(k: IndexType; var Weg :TMinEntfs; maxIndex: IndexType);
 var    BisherigeLaenge        :Real;
        Knoten                 :PKnoten;
        n                      :IndexType;
 begin
   inc(maxIndex)
   Weg[maxIndex] = k
   if erster Durchlauf then
     AktWegLaenge = 0
     BisherigeLaenge = 0
   else
     BisherigeLaenge = AktWegLaenge
     AktWegLaenge = AktWegLaenge + Kantenwert(k, Weg[maxIndex-1])
   end-if
   inc(Marke(k))
   if AktWegLaenge < Schranke then
     if ist_eine_TSP-Lösung then
       MinWeg = Weg
       Schranke = AktWegLaenge
     else
       for-each x in (Kante von Knoten(k)) do
         if Marke(Kante(k, x).ZielKnoten) < AriMax then
          Besuche(Kante(k, x).ZielKnoten, Weg, maxIndex)
         end-if
       end-for-each
     end-if
   end-if
   AktWegLaenge = BisherigeLaenge
   Weg[maxIndex] = 0
   dec(Marke(k))
 end


var      Weg                    :TMinEntfs;
         KnotenNr, WegIndex, n  :IndexType;
begin
  WegeMax = AriMax * FCount + 3
  KantenCount = 0
  Schranke = 0
  for-each x in Kanten do
    Schranke := Schranke + x.Wert
  end-for-each
  for n = 1 to KnotenAnzahl do
    Weg(n) = 0
    MinWeg(n) = 0
    Marken(n) = 0
  end-for
  Besuche(StartIndex, Weg, 0)
  Distance = Schranke
end
oben weiter

.
Beispielrealisierung (in gekürzter Fassung):

Die Implementation basiert auf diesen Definitionen.
function TGraph.TravellingSalesmanProblem(StartIndex, EndIndex: IndexType;
    var KantenCount: IndexType; var Distance :Real; AriMax: Integer): Boolean;
var      Schranke             :Real;
         HatWeg               :Boolean;
         WegeMax              :IndexType;
         AktWegLaenge         :Real;
type     TMinEntfs = array[1..1] of IndexType;
var      MinWeg               :^TMinEntfs;

 function TesteWeg(var Weg :TMinEntfs; maxIndex: IndexType): Boolean;
 var      k       :IndexType;
 begin                // über alle Knoten den Zielknoten erreicht?
  Result := (Weg[maxIndex] = EndIndex) and (maxIndex > FCount-1);
  k := 1;
  while (k <= FCount) and Result do
   begin
    Result := PointItem(k).Marke <> 0;
    inc(k);
   end;
 end;

 procedure Besuche(k: IndexType; var Weg :TMinEntfs; maxIndex: IndexType);
 var       BisherigeLaenge     :Real;
           Knoten              :PKnoten;
           n                   :IndexType;
 begin
  inc(maxIndex);
  if maxIndex >= WegeMax-2 then
   raise Exception.Create('Der Weg wurde zu lang ...');
  Weg[maxIndex] := k;
  if maxIndex = 1 then // allererster Aufruf?
   begin
    AktWegLaenge := 0;
    BisherigeLaenge := 0;
   end
  else
   begin
    BisherigeLaenge := AktWegLaenge;
    Knoten := PointItem(Weg[maxIndex-1]);
    if Knoten.KantenCount > 0 then // inc(AktWegLaenge, Kantenwert)
     for n := 0 to Knoten.KantenCount-1 do
      if Knoten.Kanten[n].DestKnoten = k then
       AktWegLaenge := AktWegLaenge + Knoten.Kanten[n].Value;
   end;

  Knoten := PointItem(k);
  inc(Knoten.Marke);
  if AktWegLaenge < Schranke then // bereits gegangener Weg innerhalb Grenze?
   if TesteWeg(Weg, maxIndex) then // Ziel bereits erreicht?
    begin                         // Aktuellen Weg umkopieren
     Move(Weg, MinWeg^, (maxIndex+1) * SizeOf(Weg[1]));
     Schranke := AktWegLaenge;
     HatWeg := True;
    end
   else                             // Besuche jeden Nachbarknoten
    if Knoten.KantenCount > 0 then
     for n := 0 to Knoten.KantenCount-1 do
      if PointItem(Knoten.Kanten[n].DestKnoten).Marke < AriMax then
       Besuche(Knoten.Kanten[n].DestKnoten, Weg, maxIndex);
  AktWegLaenge := BisherigeLaenge;     // die Rekursion rückgängig machen
  Weg[maxIndex] := 0;
  dec(PointItem(k).Marke);
 end;

 procedure CalculateSchranke;
 var       Counter, n     :IndexType;
 begin            // Summe der Gewichtung sämtlicher Kanten
  Schranke := 0;
  if FStart <> nil then
   for Counter := 1 to FCount do
    with PointItem(i)^ do
     if KantenCount > 0 then
      for n := 0 to KantenCount - 1 do
       Schranke := Schranke + Kanten[n].Value;
end;

var      Weg                      :^TMinEntfs;
         KnotenNr, WegIndex, n    :IndexType;
         Knoten                   :PKnoten;
begin
 WegeMax := AriMax * FCount + 3;
 KantenCount := 0;
 Distance := 0;
 GetMem(MinWeg, sizeof(MinWeg[1]) * WegeMax);
 try
   CalculateSchranke;
   GetMem(Weg, sizeof(Weg[1]) * WegeMax);
   try
     FillChar(Weg^, sizeof(Weg[1]) * WegeMax, 0);
     FillChar(MinWeg^, sizeof(MinWeg[1]) * WegeMax, 0);
     ZeroKnotenMarken;
     HatWeg := False;
     Besuche(StartIndex, Weg^, 0); // starten der Rekursion
     KantenCount := 0;
     ZeroKnotenMarken;
     ZeroKantenMarken;
     if HatWeg then // markieren des Weges
      begin
       KnotenNr := MinWeg[1];
       WegIndex := 2;
       Knoten := nil;
       while KnotenNr >= 1 do
        begin
         if assigned(Knoten) then
          begin
           if Knoten.KantenCount > 0 then
            for n := 0 to Knoten.KantenCount-1 do
             if Knoten.Kanten[n].DestKnoten = KnotenNr then
              inc(Knoten.Kanten[n].Marke);
           inc(Knoten.Marke);
          end;
         Knoten := PointItem(KnotenNr);
         KnotenNr := MinWeg[WegIndex];
         inc(WegIndex);
         inc(KantenCount);
        end;
       if assigned(Knoten) then
        inc(Knoten.Marke);
       Distance := Schranke;
      end;
   finally
    FreeMem(Weg, sizeof(Weg[1]) * WegeMax);
   end;
 finally
  FreeMem(MinWeg, sizeof(MinWeg[1]) * WegeMax);
 end;
 Result := HatWeg;
end;
oben weiter

.
Effizienzbetrachtung:

Die Suche nach einer Lösung des TSPs gelingt bei 15 Städten in rund 0,71 Sekunden wenn man nur ein Besuch pro Stadt zuläßt. Wenn wir zulassen, daß jede Stadt bis zu zweimal besucht werden darf, wächst der Rechenaufwand ganz erheblich auf 17,94 Sekunden. Bei gelichteten Graphen von nur zehn Städten, wie im Beispiel, braucht man bei einmaligen Besuch 0,31 Sekunden, beziehungsweise bei bis zu doppelten Besuchen 0,67 Sekunden. Die Zahl der generierten Wege bei 15 Städten und maximal einem Besuch liegt bei 483, bei bis zu zwei Besuchen sind jedoch 3.832.913 Wege erforderlich.
Bei einem Graphen mit 40 Knoten wäre so eine Laufzeit L von über 9 Monate zu erwarten. Mit jeder zusätzlicher Stadt verdoppelt sich die Laufzeit. Das bedeutet, daß auch bessere Computer oder gar der allgemeine Fortschritt in der Technik hier nicht großartig hilft. Man kann jedes Jahr ungefähr eine Stadt mehr in der gleichen Zeit berechnen. In einem Graphen, bei dem von jedem Knoten genau zwei Kanten wegführen, gibt es offensichtlich genau 2N Wege der Länge N.
 
Zeit Anzahl der maximalen Besuche eines Knotens
1 2
Anzahl der Knoten 10 0,31 s 0,67 s
15 0,71 s 17,94 s
40 9 Monate
oben Beispiel