Kürzeste Wege
In einem bewerteten Graphen ist der Weg zwischen zwei Knoten
u und
v
minimal, wenn die Summe der zwischenliegenden Kanten minimal ist, d.h.,
für u = k2,k1, ... ,kn=v
muß
S |
n-1 |
Kantenwert(ki, ki+1) |
i=1 |
minimal werden.
Ein anderer Algorithmus für Graphen, Warshalls Algorithmus zum
erzeugen der Transitiven Hülle, kann auf naheliegende Weise zu einem
Algorithmus verallgemeinert werden, der zu je zwei Knoten eines bewerteten
Graphen die minimale Entfernung bestimmt. Statt A[x][z] =
true setzt man A[x][z] = min(A[x][z],
A[x][y] + A[y][z]).
Der entsprechende Algorithmus ist auch als Floyds Algorithmus bekannt.
Da man sich gewöhnlich für die kürzeste Entfernung interessiert,
folgt hier nun ein Algorithmus, der auf eine ähnliche Idee aufbaut,
der aber den Vorteil hat zu terminieren, sobald die kürzeste Entfernung
zwischen zwei bestimmten Knoten gefunden wurde.
.
Mathematisch:
Wir suchen also die kürzeste Verbindung zwischen zwei Knoten u
und v, in einem Graphen G auf einer Menge V. Wir definieren
die Menge S aller Knoten k, für welche die kürzeste
Entfernung zu u bereits bekannt ist. Diese nennen wir dist(u,
k).
Zu Beginn des Algorithmus gilt S := {u}. In
jedem Schritt erweitert der Algorithmus die Menge S um ein neues Element.
Diese Element wird bestimmt durch die Auswertung aller Paare (k1,
k2)
mit den Eigenschaften k1ÎS,
k2ÏS,
und es existiert eine Kante von k1 nach
k2.
Wir suchen nun unter allen Paaren (k1,
k2)
mit den genannten Eigenschaften dasjenige, das den kleinsten Wert (Dist(u,
k1)
+ Kantenwert(k1, k2)) liefert.
Dieser Wert ist die kürzeste Entfernung von u nach
k2,
und wir können ein neues S bilden mit S = S +
{k2}. Wir setzen diesen Prozeß solange fort bis
vÎS
gilt.
.
Algorithmus:
for i = 1 to Knotenanzahl do MinEntf[i] = 0
S = {}
while not (Zielknoten erreicht)
MinMin = 0
minind = 0
for k1 = 1 to KnotenAnzahl do
for k2 = 1 to
KnotenAnzahl do
if
isInS(k1) and not isInS(k2) and (Existiert Kante von
k1 nach k2) then
tmp = Kante[k1, k2] + MinEntf[k1]
if tmp < MinMin then
MinMin = tmp
minind = k2
end-if
end-if
end-for k2
end-for k1
setInS(minind)
MinEntf[minind] = MinMin
end-while
.
Das heißt:
Wir berechnen für jeden Knoten die minimale Entfernung zum Knoten
u,
bis wir die für den gesuchten Knoten v haben. Für die
Entfernungsberechnung nutzen wir für jede existierende Kante die oben
bereits genannte Formel A[x][z] = min(A[x][z],
A[x][y] + A[y][z]), also wird die Entfernung
neu gesetzt, wenn die Entfernung des vorhergehenden Knotens plus der verbindenden
Kante kleiner als die bisherige ist. Daher können wir erst mit Sicherheit
die minimale Entfernung eines Knotens angeben, wenn wir die Entfernungen
aller Knoten kennen, die eine Kante zu dem Knoten besitzen. Wenn dies der
Fall ist, kann der Knoten zu der "Menge S" hinzugefügt werden.
.
Beweis (mathematisch):
Wir wollen uns noch vergewissern, daß mit Hilfe der obigen Konstruktion
auch tatsächlich der kürzeste Weg von u nach k2
gefunden wurde. Angenommen, es gäbe einen kürzeren Weg von u
nach k2, dann sei k der letzte Zwischenknoten
dieses Weges, der noch aus S ist, und k' sei sein Nachfolger.
Also hat der Weg die Struktur u...kk'...k2.
Da der Gesamtweg nach k2 angeblich kürzer ist, ist
auch der Weg von u nach k' kürzer als der bisher gefundene
Weg. Da aber die sonstigen Voraussetzungen der Konstruktion von k2
erfüllt sind, wäre das Paar (k, k') anstatt des
Paares (k, k2) von dem Algorithmus ausgewählt
worden, womit wir den Widerspruch herbeigeführt haben.
.
Beispielrealisierung (in gekürzter Fassung):
Die Implementation basiert auf
diesen Definitionen.
Die Menge S wird hier durch dem Knoten zugehörige "Marken"
dargestellt. Falls die Marke eine 0 enthält, ist der Knoten
nicht in der Menge, ansonsten ist er enthalten. Die Entfernung eines Knoten
wird im Array MinEntfs gespeichert.
function TGraph.ShortesWay(u, v: IndexType; var Distance :Real): Boolean;
type TMinEntfs = array[0..0] of Real;
var MinEntfs :^TMinEntfs;
DestReached :^Integer;
Knoten1, Knoten2 :^Knoten;
k1, k2i, k2, minind :IndexType;
MinNeu, MinMin :Real;
begin
ZeroKnotenMarken; // Menge S löschen
inc(PointItem(u).Marke); // u in Menge S einfügen
GetMem(MinEntfs, sizeof(Real) * FCount); //Speicher der Entfernung besorgen
try
FillChar(MinEntfs^, sizeof(Real) * FCount, 0); //mit Nullen füllen
DestReached := @(PointItem(v).Marke);
while DestReached^ = 0 do // solange Ziel nicht erreicht/v nicht in S
begin
MinMin := 0.0;
minind := 1;
for k1 := 1 to FCount do
begin
Knoten1 := PointItem(k1);
if (Knoten1.KantenCount > 0) and (Knoten1.Marke <> 0) then
// if Kanten vorhanden und in der Menge S
for k2i := 0 to Knoten1.KantenCount-1 do
begin
k2 := Knoten1.Kanten[k2i].DestKnoten;
Knoten2 := PointItem(k2);
if Knoten2.Marke = 0 then // if außerhalb der Menge S
begin
MinNeu := MinEntfs[k1] + Knoten1.Kanten[k2i].Value; // Neue Entfernung berechnen
if MinNeu < MinMin then // if neue Entfernung kleiner als alte
begin
MinMin := MinNeu;
minind := k2
end; // if MinNeu < MinMin
end; // if Knoten2.Marke = 0
end; // if ... for k2i := 0 to Knoten1.KantenCount-1
end; // for k1 := 1 to FCount
if MinMin <> 0.0 then
begin
inc(PointItem(minind).Marke); // Knoten in Menge S einfügen
MinEntfs[minind] := MinMin; // Minimalentfernung setzen
end;
end; // while DestReached^ = 0
Distance := MinEntfs[EndIndex]; // Ergebnis umspeichern
finally
FreeMem(MinEntfs, sizeof(Real) * FCount);
end;
end;
.
Effizienzbetrachtung:
Der Aufwand für das Hinzufügen eines neuen Knotens zu S
ist bei diesem Algorithmus N2, bei dieser Realisation
nur knapp über N, je nach Anzahl der Kanten. Dies multipliziert
sich jeweils mit der Anzahl der notwendigen Schritte S := S
+ {k2}. Im ungünstigsten Fall also N mal. Damit
haben wir eine Laufzeitverhalten kleiner-gleich N3,
bzw. maximal knapp über N2. Der Aufwand für
den angegebenen Algorithmus läßt sich in naheliegender Weise
verbessern. Im vorliegenden Fall wird die innere Schleife nur knapp über
N
mal durchlaufen. Es brauchen nur die Paare betrachtet zu werden, die durch
eine Kante verbunden sind, die aus S herausführt.
Arrayimplementation:
N2 <= Effizienz <= N3
Momentane Realisation:
(durchschn. Kantenanzahl pro Knoten) * N <= Effizienz <=
(durchschn. Kantenanzahl pro Knoten) * N2
Optimierungsvorschläge:
Wenn man nun eine Liste bildet mit allen Paaren für die das zutrifft,
dann wird der Aufwand deutlich geringer. Man muß bei Aufnahme eines
Knotens in S alle von diesem wegführenden Kanten betrachten und die
aus S herausführenden neu in die Liste aufnehmen. Eine Kante
der Liste wird im jedem Update-Schritt ausgewählt, die jeweils einen
neu hinzuzufügenden Knoten liefert. Diese muß anschließend
aus der Liste entfernt werden, da sie ja nun nicht mehr aus S herausführt.
Dijkstra gibt für eine Variante dieses Algorithmus eine Laufzeit von
N2
an.