Diese Seite ist eine freiwillige Challenge fĂŒr alle, die schneller fertig sind und mehr wollen - sie ist nicht PrĂŒfungsstoff. Wer sie durchspielt, hat am Ende den Dijkstra-Algorithmus aus der Routing-Lektion selbst programmiert und nebenbei Dictionaries gelernt, eine der wichtigsten Datenstrukturen der Informatik.
Wir bleiben beim selben Netz wie in der Routing-Lektion: fĂŒnf Router A bis E, die Zahlen sind die Kosten (z.B. Latenz).
Schreiben Sie nachbarn(kanten, k) - die Liste aller [nachbar, kosten] von Knoten k. Denken Sie an beide Richtungen: k kann an erster oder zweiter Stelle einer Kante stehen.
PythonLoading editorâŠ
kanten = [["A","B",4], ["A","C",2], ["B","D",3],
["C","D",1], ["C","E",5], ["D","E",2]]
def nachbarn(kanten, k):
ergebnis = []
for kante in kanten:
a, b, kosten = kante[0], kante[1], kante[2]
# Ist k einer der beiden Endpunkte? Dann ist der andere ein Nachbar.
...
return ergebnis
print(nachbarn(kanten, "C")) # [['A', 2], ['D', 1], ['E', 5]]
Gegeben ein Pfad wie ["A","C","D","E"] - wie teuer ist er? Summieren Sie die Kosten der aufeinanderfolgenden Kanten. Die Hilfsfunktion kante_kosten ist schon da.
PythonLoading editorâŠ
kanten = [["A","B",4], ["A","C",2], ["B","D",3],
["C","D",1], ["C","E",5], ["D","E",2]]
def kante_kosten(kanten, a, b):
for kante in kanten:
if (kante[0] == a and kante[1] == b) or (kante[0] == b and kante[1] == a):
return kante[2]
return None
def pfad_kosten(kanten, pfad):
total = 0
for i in range(len(pfad) - 1):
# Addiere die Kosten der Kante zwischen pfad[i] und pfad[i+1].
...
return total
print(pfad_kosten(kanten, ["A","C","D","E"])) # 5
kanten = [["A","B",4], ["A","C",2], ["B","D",3],
["C","D",1], ["C","E",5], ["D","E",2]]
def grad(kanten, k):
# ZĂ€hle die Kanten, an denen k beteiligt ist.
...
print(grad(kanten, "C")) # 3
Ist Ihnen etwas aufgefallen?
In jeder dieser Aufgaben mussten Sie die Kantenliste durchsuchen - immer wieder for kante in kanten. Bei Dijkstra brĂ€uchten wir das in jedem Schritt erneut. Das ist mĂŒhsam und langsam. Es gibt eine Datenstruktur, die genau dieses Problem löst.
Eine Liste greift ĂŒber die Position zu: liste[0], liste[1]. Bei Graphen ist das unpraktisch - der Knoten heisst ja "C", nicht 2.
Ein Dictionary (dict) greift ĂŒber einen SchlĂŒssel zu, z.B. einen Namen. Jeder SchlĂŒssel zeigt auf einen Wert:
PythonLoading editorâŠ
alter = {"Anna": 17, "Ben": 16} # SchlĂŒssel: Wert, SchlĂŒssel: Wert
print(alter["Anna"]) # 17 -> Zugriff ĂŒber den SchlĂŒssel
alter["Carla"] = 18 # neuen Eintrag anlegen
alter["Ben"] = 15 # bestehenden Wert Àndern
print("Ben" in alter) # True -> gibt es den SchlĂŒssel?
for name in alter: # eine Schleife lĂ€uft ĂŒber die SchlĂŒssel
print(name, "ist", alter[name])
Drei Dinge zum Merken:
Ein SchlĂŒssel kommt nur einmal vor. Erneutes Zuweisen ĂŒberschreibt den Wert.
Bauen Sie aus der Kantenliste das Adjazenz-Dictionary. FĂŒr jede Kante tragen Sie beide Richtungen ein.
PythonLoading editorâŠ
kanten = [["A","B",4], ["A","C",2], ["B","D",3],
["C","D",1], ["C","E",5], ["D","E",2]]
def kantenliste_zu_graph(kanten):
graph = {} # leeres Dictionary
for kante in kanten:
a, b, kosten = kante[0], kante[1], kante[2]
# Lege graph[a] = [] an, falls a noch nicht existiert (Tipp: if a not in graph).
# HĂ€nge dann [b, kosten] an graph[a] an - und [a, kosten] an graph[b].
...
return graph
print(kantenliste_zu_graph(kanten)["C"]) # [['A', 2], ['D', 1], ['E', 5]]
Jetzt der Kern. Wir berechnen die kĂŒrzeste Distanz vom Start zu allen Knoten - genau die Tabelle aus der Routing-Lektion. Das GerĂŒst inklusive Min-Scan ist vorgegeben; Sie fĂŒllen nur die zwei Zeilen, die die Nachbar-Distanzen aktualisieren.
PythonLoading editorâŠ
graph = {
"A": [["B",4], ["C",2]],
"B": [["A",4], ["D",3]],
"C": [["A",2], ["D",1], ["E",5]],
"D": [["B",3], ["C",1], ["E",2]],
"E": [["C",5], ["D",2]],
}
def kuerzeste_distanzen(graph, start):
unendlich = float("inf")
distanz = {}
besucht = {}
for k in graph:
distanz[k] = unendlich
besucht[k] = False
distanz[start] = 0
while True:
# 1) Unbesuchten Knoten mit der kleinsten Distanz suchen (Min-Scan).
aktuell = None
kleinste = unendlich
for k in graph:
if not besucht[k] and distanz[k] < kleinste:
kleinste = distanz[k]
aktuell = k
if aktuell is None: # kein erreichbarer Knoten mehr -> fertig
break
# 2) Alle Nachbarn prĂŒfen und ggf. eine kĂŒrzere Distanz eintragen.
for nachbar, kosten in graph[aktuell]:
neue_distanz = ... # Distanz bis aktuell + Kantenkosten
if neue_distanz < distanz[nachbar]:
... # neue, kĂŒrzere Distanz speichern
besucht[aktuell] = True # 3) Knoten abhaken
return distanz
print(kuerzeste_distanzen(graph, "A"))
# {'A': 0, 'B': 4, 'C': 2, 'D': 3, 'E': 5}
Distanzen allein sagen noch nicht, welchen Weg man nimmt. Dazu merken wir uns fĂŒr jeden Knoten den VorgĂ€nger und laufen am Ende vom Ziel rĂŒckwĂ€rts zurĂŒck. Dijkstra ist hier fertig vorgegeben - Sie bauen nur den Pfad am Schluss zusammen.
PythonLoading editorâŠ
graph = {
"A": [["B",4], ["C",2]],
"B": [["A",4], ["D",3]],
"C": [["A",2], ["D",1], ["E",5]],
"D": [["B",3], ["C",1], ["E",2]],
"E": [["C",5], ["D",2]],
}
# Ein zweites Netz mit sechs Knoten zum Testen:
graph2 = {
"A": [["C",1], ["B",2]],
"B": [["A",2], ["D",5]],
"C": [["A",1], ["E",2]],
"D": [["B",5], ["F",4], ["E",2]],
"E": [["C",2], ["F",1], ["D",2]],
"F": [["E",1], ["D",4]],
}
def kuerzester_pfad(graph, start, ziel):
unendlich = float("inf")
distanz, vorgaenger, besucht = {}, {}, {}
for k in graph:
distanz[k] = unendlich
vorgaenger[k] = None
besucht[k] = False
distanz[start] = 0
while True:
aktuell = None
kleinste = unendlich
for k in graph:
if not besucht[k] and distanz[k] < kleinste:
kleinste = distanz[k]
aktuell = k
if aktuell is None:
break
for nachbar, kosten in graph[aktuell]:
neue_distanz = distanz[aktuell] + kosten
if neue_distanz < distanz[nachbar]:
distanz[nachbar] = neue_distanz
vorgaenger[nachbar] = aktuell # merke, woher der beste Weg kam
besucht[aktuell] = True
# Pfad vom Ziel rĂŒckwĂ€rts ĂŒber die VorgĂ€nger aufbauen:
pfad = []
k = ziel
while k is not None:
... # k an den Pfad anhÀngen
k = vorgaenger[k] # einen Schritt zurĂŒckgehen
... # den Pfad umdrehen, damit er von start nach ziel zeigt
return pfad
print(kuerzester_pfad(graph, "A", "E")) # ['A', 'C', 'D', 'E']
print(kuerzester_pfad(graph2, "A", "F")) # ['A', 'C', 'E', 'F']
Noch nicht genug? (offen, ohne Auto-Korrektur)
Ausfall simulieren: Entfernen Sie aus graph2 die Kante CâE (in beiden Richtungen) und lassen Sie kuerzester_pfad(graph2, "A", "F") nochmals laufen. Welcher Weg wird jetzt gewĂ€hlt? Vergleichen Sie mit Aufgabe 2 der Routing-Lektion.
ZusammenhĂ€ngend? Schreiben Sie ist_verbunden(graph), das prĂŒft, ob man von einem Startknoten jeden anderen erreicht (Tipp: Dijkstra laufen lassen und schauen, ob eine Distanz unendlich bleibt).
Engster Knoten: Welcher Router hat den höchsten Grad - und was bedeutet sein Ausfall fĂŒrs Netz?