Алгоритм Depth First Search (DFS)

Представьте, что вы управляете роботом, который находится в некоторой вершине графа, где всего в графе n вершин и e рёбер. Вы хотите начать обход с одной вершины и постепенно пройти по всему графу, посетив все вершины, до которых можно добраться из начальной точки. Алгоритм DFS позволяет сделать это, начав с некоторой вершины, затем исследуя один путь до тех пор, пока там не останется непосещённых вершин, после чего робот «возвращается» к предыдущим узлам и начинает исследовать другие пути.

Видео от <a href="https://www.youtube.com/@Reducible">Reducible</a> — Depth First Search (DFS) Explained: Algorithm, Examples, and Code

Удобно представлять работу алгоритма DFS как перемещение маленького робота по графу, который «видит» только соседей текущей вершины и помнит все уже посещённые вершины (фактически, весь пройденный путь). Работа алгоритма рекурсивна: на каждом шаге робот переходит в новую непосещённую вершину и продолжает процесс до тех пор, пока на текущем пути не останется непроверенных вершин. Аналогия с небольшим роботом, пробирающимся по графу, очень помогает при реализации алгоритма.

Реализовать DFS можно через рекурсивный алгоритм, где мы следим за текущей вершиной (обозначим её как v) и массивом уже посещённых вершин (обозначим его как used):

import sys

sys.setrecursionlimit(10 ** 6)          # Увеличиваем лимит рекурсии, чтобы DFS работал корректно

n, e = map(int, input().split())        # n: вершины, e: рёбра
g = [[] for _ in range(n)]              # граф – список смежности
used = [False] * n                      # посетили ли вершину

for _ in range(e):                      # читаем рёбра
    a, b = map(int, input().split())    # a -> b и b -> a
    g[a].append(b)
    g[b].append(a)


def dfs(v):                             # обходим в глубину
    used[v] = True                      # отмечаем вершину как посещённую
    print(v, end=' ')                   # выводим её
    for to in g[v]:                     # идём по всем соседям v
        if not used[to]:                # если сосед не посещён
            dfs(to)                     # вызываем dfs для него


start = int(input())                    # стартовая вершина
dfs(start)                              # запускаем DFS от стартовой вершины

В начале мы инициализируем граф и запускаем обход с вершины start. Изначально все элементы в used равны False. Когда мы посещаем вершину, мы ставим used[v] = True, что означает, что вершина теперь посещена, а затем переходим к соседям текущей вершины v.

Заметьте, что рекурсия играет роль «ухода» по пути вглубь, а затем возврата к предыдущей вершине, чтобы продолжить обход соседей уже оттуда. Когда мы «возвращаемся» из рекурсивного вызова, это означает переход обратно к предыдущей вершине.

Давайте посмотрим, как алгоритм работает на нескольких примерах:

n = 4, e = 4

Входные пары a, b: [(0, 1), (0, 2), (1, 2), (3, 2)]

Начальная вершина: 3

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

  2. Исследуем соседей 3 ⇒ [2]

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

  4. Исследуем соседей 2 ⇒ [0, 1]

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

  6. Исследуем соседей 0 ⇒ [1, 2]

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

  8. Исследуем соседей 1 ⇒ [0, 2] ⇒ все посещены

  9. Возврат из 1

  10. Возврат из 0

  11. Возврат из 2

  12. Возврат из 3 ⇒ DFS завершён

  13. Готово

n = 5, e = 4

Входные пары a, b: [(0, 1), (0, 2), (1, 2), (3, 4)]

Начальная вершина: 0

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

  2. Исследуем соседей 0 ⇒ [1, 2]

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

  4. Исследуем соседей 1 ⇒ [0, 2]

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

  6. Исследуем соседей 2 ⇒ [0, 1] ⇒ все посещены

  7. Возврат из 2

  8. Возврат из 1

  9. Возврат из 0 ⇒ DFS завершён

  10. Готово

    Notice how nodes 3 and 4 were never visited.

    They were not connected to the initial node 0.

n = 10, e = 11 (пример из видео)

Входные пары a, b: [(0, 1), (0, 2), (1, 2), (1, 4), (1, 3), (3, 5), (6, 5), (5, 8), (5, 7), (7, 8), (8, 9)]

Начальная вершина: 0

  1. v = 0used = [T, F, F, F, F, F, F, F, F, F] ⇒ исследуем [1, 2]

  2. v = 1used = [T, T, F, F, F, F, F, F, F, F] ⇒ исследуем [0, 2, 3, 4]

  3. v = 2used = [T, T, T, F, F, F, F, F, F, F] ⇒ исследуем [0, 1]

  4. Возврат из 2

  5. v = 1used = [T, T, T, F, F, F, F, F, F, F] ⇒ исследуем [x, x, 3, 4]

  6. v = 3used = [T, T, T, T, F, F, F, F, F, F] ⇒ исследуем [1, 5]

  7. v = 5used = [T, T, T, T, F, T, F, F, F, F] ⇒ исследуем [3, 6, 7, 8]

  8. v = 6used = [T, T, T, T, F, T, T, F, F, F] ⇒ исследуем [5]

  9. Возврат из 6

  10. v = 7used = [T, T, T, T, F, T, T, T, F, F] ⇒ исследуем [5, 8]

  11. v = 8used = [T, T, T, T, F, T, T, T, T, F] ⇒ исследуем [5, 7, 9]

  12. v = 9used = [T, T, T, T, F, T, T, T, T, T] ⇒ исследуем [8]

  13. Возврат из 8

  14. Возврат из 7

  15. Возврат из 6

  16. Возврат из 5

  17. Возврат из 3

  18. v = 1used = [T, T, T, T, F, T, T, T, T, T] ⇒ исследуем [x, x, x, 4]

  19. v = 4used = [T, T, T, T, T, T, T, T, T, T] ⇒ исследуем [1]

  20. Возврат из 4

  21. Возврат из 1

  22. Возврат из 0

  23. Готово

Задача: Посчитать количество компонент связности в графе

Пусть дан неориентированный граф с v вершинами и e рёбрами. Нужно найти, сколько в нём компонент связности. Под «компонентой связности» понимается набор вершин, между которыми можно добраться из одной в другую.

Входные данные

Первая строка содержит два целых числа v (1 ≤ v ≤ 100 000) и e (1 ≤ e ≤ 100 000).

В следующих e строках записаны пары целых чисел v1, v2 (1 ≤ v1, v2 ≤ v), обозначающие, что вершина v1 соединена с вершиной v2 и наоборот.

Выходные данные

Программа должна вывести количество компонент связности в графе.

Примеры

Input

Output

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