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

Представьте, что вы управляете роботом, который находится в некоторой вершине графа, где всего в графе n вершин и e рёбер. Вы хотите начать обход с одной вершины и постепенно пройти по всему графу, посетив все вершины, до которых можно добраться из начальной точки. Алгоритм DFS позволяет сделать это, начав с некоторой вершины, затем исследуя один путь до тех пор, пока там не останется непосещённых вершин, после чего робот «возвращается» к предыдущим узлам и начинает исследовать другие пути.
Video preview
Видео от <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]
  1. Исследуем соседей 3 ⇒ [2]
  1. v = 2used = [False, False, True, True]
  1. Исследуем соседей 2 ⇒ [0, 1]
  1. v = 0used = [True, False, True, True]
  1. Исследуем соседей 0 ⇒ [1, 2]
  1. v = 1used = [True, True, True, True]
  1. Исследуем соседей 1 ⇒ [0, 2] ⇒ все посещены
  1. Возврат из 1
  1. Возврат из 0
  1. Возврат из 2
  1. Возврат из 3 ⇒ DFS завершён
  1. Готово
n = 5, e = 4
Входные пары a, b: [(0, 1), (0, 2), (1, 2), (3, 4)]
Начальная вершина: 0
  1. v = 0used = [True, False, False, False, False]
  1. Исследуем соседей 0 ⇒ [1, 2]
  1. v = 1used = [True, True, False, False, False]
  1. Исследуем соседей 1 ⇒ [0, 2]
  1. v = 2used = [True, True, True, False, False]
  1. Исследуем соседей 2 ⇒ [0, 1] ⇒ все посещены
  1. Возврат из 2
  1. Возврат из 1
  1. Возврат из 0 ⇒ DFS завершён
  1. Готово
    1. 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]
  1. v = 1used = [T, T, F, F, F, F, F, F, F, F] ⇒ исследуем [0, 2, 3, 4]
  1. v = 2used = [T, T, T, F, F, F, F, F, F, F] ⇒ исследуем [0, 1]
  1. Возврат из 2
  1. v = 1used = [T, T, T, F, F, F, F, F, F, F] ⇒ исследуем [x, x, 3, 4]
  1. v = 3used = [T, T, T, T, F, F, F, F, F, F] ⇒ исследуем [1, 5]
  1. v = 5used = [T, T, T, T, F, T, F, F, F, F] ⇒ исследуем [3, 6, 7, 8]
  1. v = 6used = [T, T, T, T, F, T, T, F, F, F] ⇒ исследуем [5]
  1. Возврат из 6
  1. v = 7used = [T, T, T, T, F, T, T, T, F, F] ⇒ исследуем [5, 8]
  1. v = 8used = [T, T, T, T, F, T, T, T, T, F] ⇒ исследуем [5, 7, 9]
  1. v = 9used = [T, T, T, T, F, T, T, T, T, T] ⇒ исследуем [8]
  1. Возврат из 8
  1. Возврат из 7
  1. Возврат из 6
  1. Возврат из 5
  1. Возврат из 3
  1. v = 1used = [T, T, T, T, F, T, T, T, T, T] ⇒ исследуем [x, x, x, 4]
  1. v = 4used = [T, T, T, T, T, T, T, T, T, T] ⇒ исследуем [1]
  1. Возврат из 4
  1. Возврат из 1
  1. Возврат из 0
  1. Готово
 

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

Пусть дан неориентированный граф с 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