Vorlesung 06: Graphentheorie

Grundlagen Operations Research

Prof. Dr. Knut Haase

Grundbegriffe

Netze überall

Knoten, Kanten, Pfeile

  • Knotenmenge 𝒱\mathcal{V}: die Objekte (Orte, Stationen, …).
  • Kante [i,j]∈ℰ[i, j] \in \mathcal{E}: ungerichtete Verbindung.
  • Pfeil (i,j)∈ℰ→(i, j) \in \vec{\mathcal{E}}: gerichtete Verbindung.
  • Bewertung λij\lambda_{ij}: Kosten, Distanz oder Zeit einer Kante bzw. eines Pfeils.

Ein schlichter Graph hat keine Schlingen und keine parallelen Kanten; ein Digraph ist ein endlicher, schlichter, gerichteter Graph.

Gerichtet und ungerichtet

Ungerichteter Graph mit vier Knoten und vier bewerteten Kanten ohne Pfeilspitzen: [1,2] mit 11, [1,3] mit 17, [2,3] mit 19 und [3,4] mit 13.

Ungerichteter Graph

Digraph mit denselben vier Knoten und vier bewerteten Pfeilen: (1,2) mit 21, (1,3) mit 19, (3,4) mit 33 und (4,1) mit 15. Die Pfeilspitzen geben die Richtung an.

Digraph

Spezielle Graphen

  • Zusammenhängend: unter Vernachlässigung der Richtung sind alle Knoten direkt oder indirekt verbunden.
  • Baum: zusammenhängend und ohne Kreis.
  • 1-Baum: zusammenhängend mit genau einem Kreis.

Der Kürzeste-Wege-Baum, den wir gleich berechnen, ist ein solcher Baum.

Speicherung

Warum kompakt speichern?

Eine Knoten-Knoten-Matrix braucht |𝒱|2|\mathcal{V}|^2 Einträge, bei dünn besetzten Netzen enorme Verschwendung.

Besser: nur die tatsächlich vorhandenen Pfeile speichern (Vorwärtsstern).

Vorwärtsstern

Zwei Felder genügen:

  • Ein Pfeilfeld BB listet alle Pfeile (ik,jk)(i_k, j_k) mit Bewertung λikjk\lambda_{i_k j_k}, sortiert nach Startknoten.
  • Ein Knotenfeld AA speichert je Knoten ii den Index kik_i seines ersten Pfeils in BB.

Die Nachfolger von ii stehen dann zusammenhängend zwischen kik_i und ki+1−1k_{i+1} - 1. Der Speicherbedarf sinkt von 𝒪(|𝒱|2)\mathcal{O}(|\mathcal{V}|^2) auf 𝒪(|𝒱|+|ℰ→|)\mathcal{O}(|\mathcal{V}| + |\vec{\mathcal{E}}|).

Vorwärtsstern am Beispiel

Digraph mit fünf Knoten und acht bewerteten Pfeilen: (1,2) 25, (1,3) 35, (2,4) 15, (3,2) 45, (4,3) 15, (4,5) 45, (5,3) 25 und (5,4) 35. Zwischen den Knoten 4 und 5 verläuft je ein Pfeil in beide Richtungen.

ii 1 2 3 4 5 (6)
kik_i 1 3 4 5 7 9

Knotenfeld AA

Das Pfeilfeld BB listet darunter alle acht Pfeile, nach Startknoten sortiert.

kk 1 2 3 4 5 6 7 8
(ik,jk)(i_k, j_k) (1,2) (1,3) (2,4) (3,2) (4,3) (4,5) (5,3) (5,4)
λikjk\lambda_{i_k j_k} 25 35 15 45 15 45 25 35

Nachfolger nachschlagen

kk 1 2 3 4 5 6 7 8
(ik,jk)(i_k, j_k) (1,2) (1,3) (2,4) (3,2) (4,3) (4,5) (5,3) (5,4)
λikjk\lambda_{i_k j_k} 25 35 15 45 15 45 25 35

Frage: Wie finden Sie die Nachfolger von Knoten 4?

Knotenfeld AA: k4=5k_4 = 5 und k5−1=6k_5 - 1 = 6 ⇒\Rightarrow die Pfeile (4,3)(4,3) und (4,5)(4,5), ohne die Matrix zu durchsuchen.

Der Rückwärtsstern

Für Vorgänger-Fragen (z. B. in der Netzplantechnik) sortiert man dieselben Pfeile nach dem Zielknoten:

  • Ein Pfeilfeld CC listet in Zeile nn die Nummer k̃n\tilde k_n eines Pfeils aus BB, sortiert nach Zielknoten.
  • Ein Knotenfeld DD speichert je Knoten jj den Index njn_j seines ersten Eintrags in CC.

Die Vorgänger von jj stehen dann zwischen njn_j und nj+1−1n_{j+1} - 1 in CC.

Rückwärtsstern am Beispiel

nn 1 2 3 4 5 6 7 8
k̃n\tilde k_n 1 4 2 5 7 3 8 6

Pfeilfeld CC

jj 1 2 3 4 5 (6)
njn_j 1 1 3 6 8 9

Knotenfeld DD

Die Vorgänger von Knoten 3: n3=3n_3 = 3 und n4−1=5n_4 - 1 = 5 ⇒\Rightarrow die Pfeile 2,5,72, 5, 7, also (1,3)(1,3), (4,3)(4,3) und (5,3)(5,3).

Kürzeste Wege

Aufgabenstellung

Gesucht: die kürzesten Wege von einem Startknoten qq (Quelle) zu allen anderen Knoten.

  • λij\lambda_{ij}: Bewertung des Pfeils (i,j)(i, j) (Zeit, Distanz, Kosten).
  • 𝒩(i)\mathcal{N}(i): Menge der Nachfolger von ii.
  • D[j]D[j]: bisher kürzeste Entfernung von qq nach jj; Start: D[q]=0D[q] = 0, sonst ∞\infty.
  • R[j]R[j]: bester Vorgänger von jj (zum Rekonstruieren des Weges).

Der FIFO-Algorithmus

Idee

Knoten werden in der Reihenfolge einer FIFO-Warteschlange abgearbeitet. Findet man einen kürzeren Weg zu jj, wird D[j]D[j] aktualisiert und jj 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).

Iterative Vorgehensweise

Schlange mit Kopf HH und Ende TT; Start H=T=qH = T = q.

  • Für jeden Nachfolger j∈𝒩(H)j \in \mathcal{N}(H): ist D[j]>D[H]+λH,jD[j] > D[H] + \lambda_{H,j}?
  • Falls ja: D[j]:=D[H]+λH,jD[j] := D[H] + \lambda_{H,j}, R[j]:=HR[j] := H, und jj hinten einreihen (falls nicht bereits enthalten).
  • Ist H=TH = T: Schlange leer ⇒\Rightarrow Abbruch.
  • Sonst HH auf den nächsten Knoten der Liste setzen.

Die Schlange im Feld SS

Die markierten Knoten stehen in einem Feld SS, ein Eintrag je Knoten:

  • S[j]S[j]: der Knoten, der in der Schlange auf jj folgt.
  • jj hinten einreihen: S[T]:=jS[T] := j und T:=jT := j.
  • Nächster Kopf: H:=S[H]H := S[H].

Die FIFO-Liste L hält den ganzen Lauf fest: Abgearbeitete Knoten werden gestrichen, die übrigen bilden die aktuelle Schlange.

Beispiel: Busliniennetz

Busliniennetz als Digraph mit sieben Knoten und elf bewerteten Pfeilen: (1,2) 11, (1,3) 35, (2,3) 25, (2,4) 12, (2,6) 62, (3,5) 18, (4,3) 10, (4,6) 47, (5,6) 11, (5,7) 25, (6,7) 18. Knoten 1 liegt ganz links, Knoten 7 ganz rechts.

Kürzeste Wege von Knoten q=1q = 1 zu allen anderen.

Frage: Welchen Weg vermuten Sie von 1 nach 3: direkt oder über Umwege?

Gleich ausprobieren

Auf der nächsten Folie suchen Sie die kürzesten Wege von Knoten 1 zu allen anderen Knoten. Für jeden Knoten wählen Sie den Pfeil, über den er erreicht wird. Stimmt Ihre Vermutung zu Knoten 3?

Sie sind dran

Der Ablauf im Detail

Jetzt rechnet FIFO dasselbe, Knoten für Knoten.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 ∞ ∞ ∞ ∞ ∞ ∞
R[j]R[j] – – – – – – –

L = (1)

Start: H=T=q=1H = T = q = 1.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 35 ∞ ∞ ∞ ∞
R[j]R[j] – 1 1 – – – –

L = (1, 2, 3)

Knoten 1: beide Nachfolger erhalten Marken und stellen sich hinten an.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 35 23 ∞ 73 ∞
R[j]R[j] – 1 1 2 – 2 –

L = (1, 2, 3, 4, 6)

Knoten 2: 4 und 6 sind neu. Für 3 gilt 11+25=36>3511 + 25 = 36 > 35: keine Korrektur.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 35 23 53 73 ∞
R[j]R[j] – 1 1 2 3 2 –

L = (1, 2, 3, 4, 6, 5)

Knoten 3: Knoten 5 erhält 35+18=5335 + 18 = 53.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 53 70 ∞
R[j]R[j] – 1 4 2 3 4 –

L = (1, 2, 3, 4, 6, 5, 3)

Knoten 4 korrigiert: 23+10=33<3523 + 10 = 33 < 35. Knoten 3 stellt sich erneut an! Auch D[6]=70D[6] = 70 (6 wartet bereits).

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 53 70 88
R[j]R[j] – 1 4 2 3 4 6

L = (1, 2, 3, 4, 6, 5, 3, 7)

Knoten 6: Knoten 7 erhält 70+18=8870 + 18 = 88 und stellt sich hinten an.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 53 64 78
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 6, 5, 3, 7, 6)

Knoten 5 korrigiert 6 (53+11=64<7053 + 11 = 64 < 70) und 7 (53+25=78<8853 + 25 = 78 < 88). 6 stellt sich erneut an, 7 wartet.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 64 78
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 6, 5, 3, 7, 6, 5)

Knoten 3 korrigiert 5 (33+18=51<5333 + 18 = 51 < 53). 5 stellt sich erneut an.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 64 78
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 6, 5, 3, 7, 6, 5)

Knoten 7 hat keine Nachfolger, es ändert sich nichts.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 64 78
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 6, 5, 3, 7, 6, 5)

Knoten 6 prüft 7: 64+18=82>7864 + 18 = 82 > 78, also keine Korrektur.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 62 76
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 6, 5, 3, 7, 6, 5, 6, 7)

Knoten 5 korrigiert 6 (51+11=62<6451 + 11 = 62 < 64) und 7 (51+25=76<7851 + 25 = 76 < 78). Beide stellen sich erneut an.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 62 76
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 6, 5, 3, 7, 6, 5, 6, 7)

Knoten 6 prüft 7: 62+18=80>7662 + 18 = 80 > 76, also keine Korrektur.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 62 76
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 6, 5, 3, 7, 6, 5, 6, 7)

Knoten 7 hat keine Nachfolger. Wegen H=TH = T ist die Schlange leer: Abbruch.

Ergebnis als Baum

Dasselbe Busliniennetz; rot hervorgehoben sind die sechs Pfeile des Kürzeste-Wege-Baums ab Knoten 1: (1,2), (2,4), (4,3), (3,5), (5,6) und (5,7). Knoten 3 wird also über 2 und 4 erreicht, nicht über den direkten Pfeil (1,3).

Die roten Pfeile bilden den Kürzeste-Wege-Baum.

Kürzeste Distanzen

D=(0,11,33,23,51,62,76) D = (0,\, 11,\, 33,\, 23,\, 51,\, 62,\, 76)

FIFO-LIFO

Der FIFO-LIFO-Algorithmus

Idee

Ein Knoten in der Liste löst Korrekturen bei seinen Nachfolgern aus, bei deren Nachfolgern usw. Muss er erneut hinein, lohnt es nicht, erst die Knoten abzuarbeiten, die er vorher hineingebracht hat: Sie werden voraussichtlich wieder korrigiert.

Wer zum wiederholten Mal in die Liste kommt, wird deshalb vorne hinter dem Kopf eingereiht: das FIFO-LIFO-Prinzip.

Vorne oder hinten?

Frage: Woran erkennen Sie, dass ein Knoten schon einmal in der Liste stand?

Erkennungsregel

Bis auf qq starten alle Knoten mit D[j]=∞D[j] = \infty. Ist D[j]D[j] vor der Korrektur schon endlich, stand jj schon in der Liste. Steht er nicht mehr in der Schlange, ist er bereits bearbeitet: Er kommt vorne hin.

Ein zusätzliches Feld ginge auch, ist so aber nicht nötig.

Vorne einreihen im Feld SS

Ausschnitt aus einem Lauf mit sieben Knoten:

jj 1 2 3 4 5 6 7
S[j]S[j] – – 6 – 3 4 –

L = (5, 3, 6, 4)

Kopf H=5H = 5, Ende T=4T = 4. Knoten 1 ist zum zweiten Mal aufzunehmen.

jj 1 2 3 4 5 6 7
S[j]S[j] 3 – 6 – 1 4 –

L = (5, 1, 3, 6, 4)

S[1]:=S[5]=3S[1] := S[5] = 3, dann S[5]:=1S[5] := 1 (allgemein S[j]:=S[H]S[j] := S[H] und S[H]:=jS[H] := j).

Ablauf von FIFO-LIFO

Start wie bei FIFO: H=T=qH = T = q.

  • Für jeden Nachfolger j∈𝒩(H)j \in \mathcal{N}(H): ist D[j]>D[H]+λH,jD[j] > D[H] + \lambda_{H,j}?
  • Falls ja: D[j]:=D[H]+λH,jD[j] := D[H] + \lambda_{H,j}, R[j]:=HR[j] := H; steht jj nicht in der Schlange, wird er eingereiht:
  • hinten, falls bisher D[j]=∞D[j] = \infty: S[T]:=jS[T] := j, T:=jT := j;
  • sonst vorne hinter HH: S[j]:=S[H]S[j] := S[H], S[H]:=jS[H] := j (ist H=TH = T, auch T:=jT := j).
  • Ist H=TH = T: Abbruch; sonst H:=S[H]H := S[H].

FIFO-LIFO am Busliniennetz

Dasselbe Netz, aber wer erneut markiert wird, kommt vorne hin.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 ∞ ∞ ∞ ∞ ∞ ∞
R[j]R[j] – – – – – – –

L = (1)

Start: H=T=q=1H = T = q = 1.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 35 ∞ ∞ ∞ ∞
R[j]R[j] – 1 1 – – – –

L = (1, 2, 3)

Knoten 1: beide Nachfolger erhalten erstmals Marken und stellen sich hinten an.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 35 23 ∞ 73 ∞
R[j]R[j] – 1 1 2 – 2 –

L = (1, 2, 3, 4, 6)

Knoten 2: 4 und 6 sind neu und kommen hinten hin. Für 3 gilt 11+25=36>3511 + 25 = 36 > 35: keine Korrektur.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 35 23 53 73 ∞
R[j]R[j] – 1 1 2 3 2 –

L = (1, 2, 3, 4, 6, 5)

Knoten 3: Knoten 5 erhält 35+18=5335 + 18 = 53 und stellt sich hinten an.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 53 70 ∞
R[j]R[j] – 1 4 2 3 4 –

L = (1, 2, 3, 4, 3, 6, 5)

Knoten 4 korrigiert 3 (23+10=33<3523 + 10 = 33 < 35). D[3]D[3] war endlich: 3 kommt vorne hin. Auch D[6]=70D[6] = 70 (6 wartet).

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 70 ∞
R[j]R[j] – 1 4 2 3 4 –

L = (1, 2, 3, 4, 3, 6, 5)

Knoten 3 ist sofort wieder dran und korrigiert 5 (33+18=51<5333 + 18 = 51 < 53), bevor 5 überhaupt bearbeitet wird.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 70 88
R[j]R[j] – 1 4 2 3 4 6

L = (1, 2, 3, 4, 3, 6, 5, 7)

Knoten 6: Knoten 7 erhält erstmals 70+18=8870 + 18 = 88 und stellt sich hinten an.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 62 76
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 3, 6, 5, 6, 7)

Knoten 5 korrigiert 6 (51+11=62<7051 + 11 = 62 < 70) und 7 (51+25=76<8851 + 25 = 76 < 88). 6 kommt vorne hin, 7 wartet.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 62 76
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 3, 6, 5, 6, 7)

Knoten 6 prüft 7: 62+18=80>7662 + 18 = 80 > 76, also keine Korrektur.

jj 1 2 3 4 5 6 7
D[j]D[j] 0 11 33 23 51 62 76
R[j]R[j] – 1 4 2 3 5 5

L = (1, 2, 3, 4, 3, 6, 5, 6, 7)

Knoten 7 hat keine Nachfolger. Wegen H=TH = T ist die Schlange leer: Abbruch.

Varianten des Verfahrens

  • FIFO-LIFO braucht am Busliniennetz 9 statt 12 Iterationen bei gleichen Distanzen: 5 und 7 kommen nur einmal dran. Das spart nur, wenn Knoten erneut in die Schlange kommen; sonst sind beide gleich schnell.
  • FIFO-FIFO: mehrere erneut aufzunehmende Knoten behalten ihre Reihenfolge, eingefügt hinter dem zuletzt eingefügten Knoten.

Beide Varianten reduzieren überflüssige Aktualisierungen.

Netzwerkfluss

Kürzeste Wege als LP

Kürzeste Wege lassen sich als Netzwerkflussproblem formulieren: Wir schicken von qq eine Einheit zu jedem anderen Knoten.

  • cijc_{ij}: Kosten pro Mengeneinheit auf Pfeil (i,j)(i, j).
  • XijX_{ij}: Transportmenge (Fluss) auf Pfeil (i,j)(i, j).

Ist nur der kürzeste Weg zu einem Zielknoten ss (Senke) gesucht, fließt genau eine Einheit von qq nach ss.

Das Modell

Minimiere F=∑(i,j)∈ℰ→cijXij \text{Minimiere } F = \sum_{(i,j) \in \vec{\mathcal{E}}} c_{ij}\, X_{ij}

Flusserhaltung (Zufluss −- Abfluss == Bedarf) für jeden Knoten ii:

∑j:(j,i)Xji−∑j:(i,j)Xij={1−|𝒱|i=q1sonst \sum_{j:(j,i)} X_{ji} - \sum_{j:(i,j)} X_{ij} = \begin{cases} 1 - |\mathcal{V}| & i = q \\ 1 & \text{sonst} \end{cases}

Das Modell am Beispiel

Minimiere F=11X12+35X13+25X23+…+18X67 \text{Minimiere } F = 11 X_{12} + 35 X_{13} + 25 X_{23} + \ldots + 18 X_{67}

Flusserhaltung, links die Knoten 1 bis 4, rechts 5 bis 7:

−X12−X13=−6X12−X23−X24−X26=1X13+X23+X43−X35=1X24−X43−X46=1 \begin{aligned} -X_{12} - X_{13} &= -6 \\ X_{12} - X_{23} - X_{24} - X_{26} &= 1 \\ X_{13} + X_{23} + X_{43} - X_{35} &= 1 \\ X_{24} - X_{43} - X_{46} &= 1 \end{aligned}

X35−X56−X57=1X26+X46+X56−X67=1X57+X67=1X12,X13,…,X67≥0 \begin{aligned} X_{35} - X_{56} - X_{57} &= 1 \\ X_{26} + X_{46} + X_{56} - X_{67} &= 1 \\ X_{57} + X_{67} &= 1 \\ X_{12}, X_{13}, \ldots, X_{67} &\ge 0 \end{aligned}

Kontrolle mit Julia

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[3]: 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 optimale Fluss

Dasselbe Busliniennetz, hervorgehoben sind wieder die Pfeile (1,2), (2,4), (4,3), (3,5), (5,6) und (5,7): Der optimale Fluss läuft genau über den Kürzeste-Wege-Baum, alle übrigen Pfeile bleiben ungenutzt.

Der Fluss nutzt genau den Kürzeste-Wege-Baum:

X12=6X_{12} = 6, X24=5X_{24} = 5, X43=4X_{43} = 4, X35=3X_{35} = 3, X56=1X_{56} = 1, X57=1X_{57} = 1.

Schöner Zusammenhang

F*=256=∑jD[j] F^* = 256 = \sum_j D[j] Die Flusskosten sind die Summe aller kürzesten Entfernungen.

Zusammenfassung

Das Wichtigste auf einen Blick

  • Ein Graph besteht aus Knoten und (gerichteten) Kanten mit Bewertungen.
  • Die kompakte Speicherung (Vorwärtsstern) spart bei dünnen Netzen enorm Speicher.
  • Der FIFO-Algorithmus bestimmt kürzeste Wege von qq zu allen Knoten (Label-Correcting).
  • FIFO-LIFO reiht erneut aufzunehmende Knoten vorne ein und spart so Iterationen, wenn Marken korrigiert werden.
  • Kürzeste Wege sind ein Spezialfall des Netzwerkflussproblems, lösbar als LP.

Literatur

Literaturverzeichnis

Domschke, Wolfgang, Andreas Drexl, Robert Klein, und Armin Scholl. 2015. Einführung in Operations Research. 9. Aufl. Springer Gabler. https://doi.org/10.1007/978-3-662-48216-2.
Vlćek, Tobias, Knut Haase, Malte Fliedner, und Tobias Cors. 2024. „Police Service District Planning“. OR Spectrum 46 (4): 1029–61. https://doi.org/10.1007/s00291-024-00745-3.