Tiefensuche (Depth First Search, DFS)

Stellen Sie sich vor, Sie steuern einen Roboter, der sich an einem bestimmten Ort in einem Graphen mit n Knoten und e Kanten befindet. Sie möchten von einem Startknoten aus nach und nach den gesamten Graphen erkunden und alle erreichbaren Knoten besuchen. Der DFS-Algorithmus macht genau das, indem er von einem Startknoten aus einen Pfad so weit wie möglich verfolgt. Sobald es dort keine neuen Knoten mehr gibt, geht er auf die vorherigen Knoten zurück und sucht von dort neue, noch nicht besuchte Wege.

Video von Reducible – Depth First Search (DFS) erklärt: Algorithmus, Beispiele und Code

Eine hilfreiche Vorstellung für den DFS-Algorithmus ist die Idee eines Roboters, der von Knoten zu Knoten geht. Er kann nur die Nachbarn des aktuellen Knotens und jene Knoten sehen, die er schon besucht hat (er behält also den gesamten Weg im Blick). Dieses Vorgehen ist ein rekursiver Prozess: In jedem Schritt bewegt sich der Roboter zu einem bisher unbesuchten Knoten und wiederholt das Vorgehen, bis kein weiterer unentdeckter Knoten mehr auf diesem Pfad liegt. Wenn man sich das als kleinen Roboter vorstellt, ist die Umsetzung oft einfacher.

Die Implementierung des DFS-Algorithmus kann mithilfe einer rekursiven Funktion umgesetzt werden. Dabei behalten wir den aktuellen Knoten (den wir mit v bezeichnen) und alle bereits besuchten Knoten (in used) im Blick:

import sys

sys.setrecursionlimit(10 ** 6)          # Erhöht das Rekursionslimit, damit DFS korrekt ausgeführt werden kann

n, e = map(int, input().split())        # n: Knoten, e: Kanten
g = [[] for _ in range(n)]              # Graph - Adjazenzliste
used = [False] * n                      # bereits besuchte Knoten

for _ in range(e):                      # Kanten einlesen
    a, b = map(int, input().split())    # a -> b und b -> a
    g[a].append(b)
    g[b].append(a)


def dfs(v):                             # Tiefensuche
    used[v] = True                      # Knoten als besucht markieren
    print(v, end=' ')                   # Knoten ausgeben
    for to in g[v]:                     # für jeden mit v verbundenen Knoten
        if not used[to]:                # falls Knoten noch nicht besucht
            dfs(to)                     # DFS für diesen Knoten aufrufen


start = int(input())                    # Startknoten einlesen
dfs(start)                              # DFS vom Startknoten aus aufrufen

Zunächst initialisieren wir also den Graphen und beginnen die Traversierung beim Knoten start. Alle Werte in used sind anfangs False. Sobald wir einen Knoten besuchen, setzen wir ihn mit used[v] = True auf besucht und gehen dann alle Nachbarn des aktuellen Knotens v durch.

Die Rekursion übernimmt dabei die Rolle, den Weg zu erkunden. Sobald ein Pfad erschöpft ist, „kehrt“ die Funktion zur vorherigen Position zurück, um von dort aus die übrigen Nachbarn zu durchsuchen. Wenn wir uns mit der Rückkehr aus der Rekursion gedanklich wieder zum Knoten davor „bewegen“, verstehen wir leicht, wie der Algorithmus arbeitet.

Werfen wir einen Blick auf einige Eingaben und deren Abläufe:

n = 4, e = 4

Die eingegebenen Paare a, b lauten: [(0, 1), (0, 2), (1, 2), (3, 2)]

Der Startknoten ist 3

  1. v = 3used = [False, False, False, True]

  2. Durchsuche alle Nachbarn von 3 ⇒ [2]

  3. v = 2used = [False, False, True, True]

  4. Durchsuche alle Nachbarn von 2 ⇒ [0, 1]

  5. v = 0used = [True, False, True, True]

  6. Durchsuche alle Nachbarn von 0 ⇒ [1, 2]

  7. v = 1used = [True, True, True, True]

  8. Durchsuche alle Nachbarn von 1 ⇒ [0, 2] ⇒ alle besucht

  9. Zurückkehren von 1

  10. Zurückkehren von 0

  11. Zurückkehren von 2

  12. Zurückkehren von 3 → DFS beendet

  13. Fertig

n = 5, e = 4

Die eingegebenen Paare a, b lauten: [(0, 1), (0, 2), (1, 2), (3, 4)]

Der Startknoten ist 0

  1. v = 0used = [True, False, False, False, False]

  2. Durchsuche alle Nachbarn von 0 ⇒ [1, 2]

  3. v = 1used = [True, True, False, False, False]

  4. Durchsuche alle Nachbarn von 1 ⇒ [0, 2]

  5. v = 2used = [True, True, True, False, False]

  6. Durchsuche alle Nachbarn von 2 ⇒ [0, 1] ⇒ alle besucht

  7. Zurückkehren von 2

  8. Zurückkehren von 1

  9. Zurückkehren von 0 → DFS beendet

  10. Fertig

    Notice how nodes 3 and 4 were never visited.

    They were not connected to the initial node 0.

n = 10, e = 11 (Beispiel aus dem Video)

Die eingegebenen Paare a, b lauten: [(0, 1), (0, 2), (1, 2), (1, 4), (1, 3), (3, 5), (6, 5), (5, 8), (5, 7), (7, 8), (8, 9)]

Der Startknoten ist 0

  1. v = 0used = [T, F, F, F, F, F, F, F, F, F] ⇒ erkunde [1, 2]

  2. v = 1used = [T, T, F, F, F, F, F, F, F, F] ⇒ erkunde [0, 2, 3, 4]

  3. v = 2used = [T, T, T, F, F, F, F, F, F, F] ⇒ erkunde [0, 1]

  4. Zurückkehren von 2

  5. v = 1used = [T, T, T, F, F, F, F, F, F, F] ⇒ erkunde [x, x, 3, 4]

  6. v = 3used = [T, T, T, T, F, F, F, F, F, F] ⇒ erkunde [1, 5]

  7. v = 5used = [T, T, T, T, F, T, F, F, F, F] ⇒ erkunde [3, 6, 7, 8]

  8. v = 6used = [T, T, T, T, F, T, T, F, F, F] ⇒ erkunde [5]

  9. Zurückkehren von 6

  10. v = 7used = [T, T, T, T, F, T, T, T, F, F] ⇒ erkunde [5, 8]

  11. v = 8used = [T, T, T, T, F, T, T, T, T, F] ⇒ erkunde [5, 7, 9]

  12. v = 9used = [T, T, T, T, F, T, T, T, T, T] ⇒ erkunde [8]

  13. Zurückkehren von 8

  14. Zurückkehren von 7

  15. Zurückkehren von 6

  16. Zurückkehren von 5

  17. Zurückkehren von 3

  18. v = 1used = [T, T, T, T, F, T, T, T, T, T] ⇒ erkunde [x, x, x, 4]

  19. v = 4used = [T, T, T, T, T, T, T, T, T, T] ⇒ erkunde [1]

  20. Zurückkehren von 4

  21. Zurückkehren von 1

  22. Zurückkehren von 0

  23. Fertig

Herausforderung: Anzahl der zusammenhängenden Komponenten zählen

Gegeben ist ein ungerichteter Graph mit v Knoten und e Kanten. Ihre Aufgabe ist es, die Anzahl der zusammenhängenden Komponenten dieses Graphen zu bestimmen. Eine Menge von Knoten gilt als eine zusammenhängende Komponente, wenn man innerhalb dieser Menge von jedem Knoten jeden anderen Knoten erreichen kann.

Eingabe

Die erste Zeile der Eingabe enthält zwei ganze Zahlen v (1 ≤ v ≤ 100 000) und e (1 ≤ e ≤ 100 000).

Die folgenden e Zeilen enthalten jeweils ein Paar Ganzzahlen v1, v2 (1 ≤ v1, v2 ≤ v). Dies bedeutet, dass der Knoten v1 mit dem Knoten v2 verbunden ist und umgekehrt.

Ausgabe

Das Programm soll die Anzahl der zusammenhängenden Komponenten des Graphen ausgeben.

Beispiele

Eingabe

Ausgabe

7 6 1 2 2 3 1 3 4 5 5 6 4 6

3

2 1 1 2

1

2 0

2

Constraints

Time limit: 5 seconds

Memory limit: 512 MB

Output limit: 1 MB

To check your solution you need to sign in
Sign in to continue