Grundlagen Operations Research
Vlćek u. a. (2024)
Ein schlichter Graph hat keine Schlingen und keine parallelen Kanten; ein Digraph ist ein endlicher, schlichter, gerichteter Graph.
Ungerichteter Graph
Digraph
Der Kürzeste-Wege-Baum, den wir gleich berechnen, ist ein solcher Baum.
Eine Knoten-Knoten-Matrix braucht Einträge, bei dünn besetzten Netzen enorme Verschwendung.
Besser: nur die tatsächlich vorhandenen Pfeile speichern (Vorwärtsstern).
Zwei Felder genügen:
Die Nachfolger von stehen dann zusammenhängend zwischen und . Der Speicherbedarf sinkt von auf .
| 1 | 2 | 3 | 4 | 5 | (6) | |
|---|---|---|---|---|---|---|
| 1 | 3 | 4 | 5 | 7 | 9 |
Knotenfeld
Das Pfeilfeld listet darunter alle acht Pfeile, nach Startknoten sortiert.
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
|---|---|---|---|---|---|---|---|---|
| (1,2) | (1,3) | (2,4) | (3,2) | (4,3) | (4,5) | (5,3) | (5,4) | |
| 25 | 35 | 15 | 45 | 15 | 45 | 25 | 35 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
|---|---|---|---|---|---|---|---|---|
| (1,2) | (1,3) | (2,4) | (3,2) | (4,3) | (4,5) | (5,3) | (5,4) | |
| 25 | 35 | 15 | 45 | 15 | 45 | 25 | 35 |
Frage: Wie finden Sie die Nachfolger von Knoten 4?
Knotenfeld : und die Pfeile und , ohne die Matrix zu durchsuchen.
Für Vorgänger-Fragen (z. B. in der Netzplantechnik) sortiert man dieselben Pfeile nach dem Zielknoten:
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
|---|---|---|---|---|---|---|---|---|
| 1 | 4 | 2 | 5 | 7 | 3 | 8 | 6 |
Feld : Pfeilindizes, nach Zielknoten sortiert. Dazu ein Einstiegsfeld je Zielknoten ().
Die Vorgänger von Knoten 3: Positionen bis Pfeile , also , und .
Gesucht: die kürzesten Wege von einem Startknoten zu allen anderen Knoten.
Idee
Knoten werden in der Reihenfolge einer FIFO-Warteschlange abgearbeitet. Findet man einen kürzeren Weg zu , wird aktualisiert und hinten angestellt: solange, bis die Schlange leer ist.
Ein Knoten steht höchstens einmal gleichzeitig in der Schlange, kann aber insgesamt mehrfach bearbeitet werden (Label-Correcting).
Schlange mit Kopf und Ende ; Start .
Kürzeste Wege von Knoten zu allen anderen.
Frage: Welchen Weg vermuten Sie von 1 nach 3: direkt oder über Umwege?
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|
| 0 | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | |
| – | – | – | – | – | – | – |
L = (1)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|
| 0 | 11 | 35 | ∞ | ∞ | ∞ | ∞ | |
| – | 1 | 1 | – | – | – | – |
L = (1, 2, 3)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|
| 0 | 11 | 35 | 23 | ∞ | 73 | ∞ | |
| – | 1 | 1 | 2 | – | 2 | – |
L = (1, 2, 3, 4, 6)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|
| 0 | 11 | 35 | 23 | 53 | 73 | ∞ | |
| – | 1 | 1 | 2 | 3 | 2 | – |
L = (1, 2, 3, 4, 6, 5)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|
| 0 | 11 | 33 | 23 | 53 | 70 | ∞ | |
| – | 1 | 4 | 2 | 3 | 4 | – |
L = (1, 2, 3, 4, 6, 5, 3)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|
| 0 | 11 | 33 | 23 | 51 | 62 | 76 | |
| – | 1 | 4 | 2 | 3 | 5 | 5 |
L = (1, 2, 3, 4, 6, 5, 3, 7, 6, 5, 6, 7)
Start: .
Knoten 1: beide Nachfolger erhalten Marken und stellen sich hinten an.
Knoten 2: 4 und 6 sind neu. Für 3 gilt : keine Korrektur.
Knoten 3: Knoten 5 erhält .
Knoten 4 korrigiert: . Knoten 3 stellt sich erneut an! Auch (6 wartet bereits).
So läuft es weiter (5 korrigiert 6 und 7, das erneute 3 korrigiert 5, …), bis die Schlange leer ist.
Die roten Pfeile bilden den Kürzeste-Wege-Baum.
Kürzeste Distanzen
Beide Varianten reduzieren überflüssige Aktualisierungen.
Kürzeste Wege lassen sich als Netzwerkflussproblem formulieren: Wir schicken von eine Einheit zu jedem anderen Knoten.
Flusserhaltung (Zufluss Abfluss Bedarf) für jeden Knoten :
using JuMP, HiGHS
kanten = [(1,2,11),(1,3,35),(2,3,25),(2,4,12),(4,3,10),(3,5,18),
(2,6,62),(4,6,47),(5,6,11),(5,7,25),(6,7,18)]
V, q = 1:7, 1
modell = Model(HiGHS.Optimizer); set_silent(modell)
@variable(modell, X[e in kanten] >= 0) # Fluss je Pfeil
@objective(modell, Min, sum(e[3] * X[e] for e in kanten)) # e = (i, j, Kosten)
for i in V # Flusserhaltung
zu = sum(X[e] for e in kanten if e[2] == i; init = 0.0)
ab = sum(X[e] for e in kanten if e[1] == i; init = 0.0)
@constraint(modell, zu - ab == (i == q ? 1 - length(V) : 1))
end
optimize!(modell)
println("Gesamtkosten des Flusses: F* = ", objective_value(modell))Gesamtkosten des Flusses: F* = 256.0
Der Fluss nutzt genau den Kürzeste-Wege-Baum:
, , , , , .
Schöner Zusammenhang
Die Flusskosten sind die Summe aller kürzesten Entfernungen.
Vorlesung 06: Graphentheorie | Zur Webseite