Обход графа в глубину (DFS) (Олимпиадное программирование)
← К списку тем
Алгоритмы на графах

Обход графа в глубину (DFS)

Как пройти по связям, ничего не пропустить и понять, когда пора вернуться назад? Разберём на шести вершинах и проверим себя.

  • Граф и соседи
  • Шаги DFS
  • C++ и Python
  • Задачи Codeforces

Представь прогулку по комнатам

Вершины графа — комнаты с номерами. Рёбра — проходы между ними. Из комнаты можно перейти только в соседнюю. DFS идёт по очередному проходу как можно дальше; если дальше нет непосещённых комнат, возвращается туда, откуда пришёл.

  1. Отметь начальную вершину посещённой.
  2. Выбери её соседа, которого ещё не посещал, и повтори то же действие для него.
  3. Когда новых соседей нет, вернись на одну вершину назад.

Зачем отмечать посещённые вершины? В графе может быть цикл: без отметки можно бесконечно ходить по кругу. Вершину отмечают сразу при входе в неё.

Пройди граф шаг за шагом

Обход начнётся с вершины 1. Соседей каждой вершины рассматриваем по возрастанию номеров. Если один участок графа закончился, начнём DFS из первой ещё не посещённой вершины.

1 2 3 4 5 6
не посещенав текущем путиобработанашаг

Готовы начать

Ещё ни одна вершина не посещена. Нажми «Следующий шаг».

Текущий путь—
Порядок первого посещения—
Найдено связных частей0
Шаг 0
Если кнопки не работают: ответ для этого графа

Порядок первого посещения при выборе меньшего соседа: 1 → 2 → 4 → 3 → 5 → 6. Сначала закончится часть с вершинами 1–4; затем обход начнётся с вершины 5. Связных частей две.

Что показал обход

В одной компоненте связности находятся все вершины, между которыми можно пройти по рёбрам. В примере это группы {1, 2, 3, 4} и {5, 6}. Чтобы проверить весь граф, запускаем DFS заново из каждой ещё не отмеченной вершины.

Текущий путь в тренажёре похож на стопку: вошли в новую вершину — положили её наверх; закончили с соседями — убрали и вернулись к предыдущей. Именно так работают рекурсивные вызовы.

Если выбирать соседей в другом порядке, порядок посещения изменится. Состав компонент останется прежним. DFS сам по себе не гарантирует кратчайший путь по числу рёбер; для этого в невзвешенном графе обычно используют BFS.

Как быстро работает DFS?

При хранении соседей в списках сам обход занимает O(n + m): каждая вершина отмечается один раз, а каждое ребро проверяется не больше двух раз в неориентированном графе. Здесь n — число вершин, m — число рёбер. В учебном коде соседи дополнительно сортируются, чтобы получить определённый порядок посещения.

Проверь себя

Пользуйся тем же графом и тем же правилом: первым выбирается сосед с меньшим номером.

В каком порядке вершины будут посещены впервые?

 

Объяснение ответа

Из 1 переходим в 2, затем в 4 и 3. Когда вернулись в 1, её сосед 3 уже посещён. Оставшиеся вершины 5 и 6 образуют вторую компоненту. Значит, порядок 1, 2, 4, 3, 5, 6, частей 2.

Запишем DFS на C++ и Python

Для каждого ребра a b добавляем b в список соседей a и наоборот: граф неориентированный. Списки сортируем, чтобы пример совпал с симуляцией.

Пример ввода
6 5
1 2
1 3
2 4
3 4
5 6
Вывод программ
1 2 4 3 5 6
2
C++ · рекурсивный DFS
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

vector<vector<int>> graph;
vector<int> visited, order;

void dfs(int v) {
 visited[v] = 1; // уже были здесь
 order.push_back(v); // записали первый визит
 for (int u : graph[v]) {
 if (!visited[u]) dfs(u);
 }
}

int main() {
 int n, m;
 cin >> n >> m;
 graph.resize(n + 1);
 visited.assign(n + 1, 0);
 for (int i = 0; i < m; i++) {
 int a, b;
 cin >> a >> b;
 graph[a].push_back(b);
 graph[b].push_back(a);
 }
 for (int v = 1; v <= n; v++) {
 sort(graph[v].begin(), graph[v].end());
 }
 int components = 0;
 for (int v = 1; v <= n; v++) {
 if (!visited[v]) {
 components++;
 dfs(v);
 }
 }
 for (int i = 0; i < n; i++) {
 if (i) cout << ' ';
 cout << order[i];
 }
 cout << '\n' << components << '\n';
}
Python · рекурсивный DFS
n, m = map(int, input().split())
graph = [[] for _ in range(n + 1)]
for _ in range(m):
 a, b = map(int, input().split())
 graph[a].append(b)
 graph[b].append(a)

for v in range(1, n + 1):
 graph[v].sort()

visited = [False] * (n + 1)
order = []

def dfs(v):
 visited[v] = True # уже были здесь
 order.append(v) # записали первый визит
 for u in graph[v]:
 if not visited[u]:
 dfs(u)

components = 0
for v in range(1, n + 1):
 if not visited[v]:
 components += 1
 dfs(v)

print(*order)
print(components)

Этот код показывает рекурсию на небольших графах (Симулятор DFS). У цепочки из десятков тысяч вершин глубина вызовов тоже может быть огромной. Для таких входных данных удобнее заменить рекурсию собственным стеком.

Как обойти большую компоненту без рекурсии?

Уже построив graph, вызывай такую функцию для каждой ещё не посещённой вершины. Это версия для поиска достижимых вершин и компонент; порядок посещения может отличаться от рекурсивного примера выше.

C++ · явный стек
void explore(int start,
 const vector<vector<int>>& graph,
 vector<int>& visited) {
 vector<int> st = {start};
 visited[start] = 1;
 while (!st.empty()) {
 int v = st.back();
 st.pop_back();
 for (int u : graph[v]) {
 if (!visited[u]) {
 visited[u] = 1;
 st.push_back(u);
 }
 }
 }
}
Python · явный стек
def explore(start, graph, visited):
 st = [start]
 visited[start] = True
 while st:
 v = st.pop()
 for u in graph[v]:
 if not visited[u]:
 visited[u] = True
 st.append(u)

Задачи Codeforces для практики

Иди по порядку. Ниже приведены краткие условия по-русски и прямые ссылки на оригиналы. В каждой задаче DFS даёт полезный способ решения, но одного шаблона для всех задач недостаточно.

Начни здесь · рейтинг 900

115A — Вечеринка (Party)

У каждого из n сотрудников либо есть непосредственный начальник, либо начальника нет. Нужно разделить всех на как можно меньше групп так, чтобы в одной группе никто не оказался вместе со своим руководителем — прямым или руководителем через цепочку начальников.

Ввод: 1 ≤ n ≤ 2000, затем для каждого сотрудника номер начальника или -1. Циклов подчинения нет. Вывод: минимальное число групп.

Подсказка к DFS

Начни обход от каждого человека без начальника; передавай детям глубину. Ответ — наибольшее число уровней в деревьях подчинения.

Открыть Party на Codeforces ↗
Компоненты · рейтинг 1200

217A — Катание на коньках (Ice Skating)

На поле стоят сугробы. Конькобежец может скользить по вертикали или горизонтали от одного сугроба к другому. Какое минимальное число новых сугробов нужно добавить в точках с целыми координатами, чтобы из любого существующего сугроба можно было добраться до любого другого?

Ввод: 1 ≤ n ≤ 100, затем n пар различных координат, каждое число от 1 до 1000. Вывод: минимум новых сугробов.

Подсказка к DFS

Считай сугробы вершинами: два существующих сугроба соединены, если у них совпадает x или y. DFS найдёт компоненты. Подумай, сколько новых сугробов нужно, чтобы соединить k таких частей.

Открыть Ice Skating на Codeforces ↗
Компоненты и стоимость · рейтинг 1300

893C — Слух (Rumor)

Жители дружат попарно. Если заплатить одному жителю, он расскажет новость друзьям, те передадут её дальше бесплатно. У каждого жителя своя цена. Сколько золота нужно потратить, чтобы новость дошла до всех?

Ввод: 1 ≤ n ≤ 100000, 0 ≤ m ≤ 100000; цены жителей от 0 до 10⁹, затем m пар друзей. Вывод: минимальная общая стоимость.

Подсказка к DFS

Обойди каждую компоненту и найди в ней наименьшую цену. Сложи эти цены. Сумма может не поместиться в 32-битное целое число; в C++ используй long long.

Открыть Rumor на Codeforces ↗
Цикл в сетке · рейтинг 1500

510B — Лис и две точки (Fox And Two Dots)

Дано поле с буквами-цветами. Можно переходить между клетками с общей стороной. Существует ли замкнутый путь по клеткам одного цвета, состоящий хотя бы из четырёх разных клеток?

Ввод: размеры 2 ≤ n, m ≤ 50 и n строк по m заглавных латинских букв. Вывод: Yes или No.

Подсказка к DFS

Посещай только соседние клетки того же цвета. Если дошёл до уже посещённой клетки, которая не является клеткой, откуда только что пришёл, найден цикл.

Открыть Fox And Two Dots на Codeforces ↗
Путь в дереве · рейтинг 1500

580C — Кефа и парк (Kefa and Park)

Вершина 1 — дом, а в листьях дерева находятся рестораны. Некоторые вершины заняты котами. Нужно посчитать рестораны, до которых можно дойти от дома, ни разу не встретив больше m подряд вершин с котами.

Ввод: 2 ≤ n ≤ 100000, 1 ≤ m ≤ n; для каждой вершины указано, есть ли кот, затем n − 1 ребро дерева. Вывод: число подходящих ресторанов.

Подсказка к DFS

Передавай вместе с текущей вершиной длину цепочки котов. На вершине без кота сбрасывай её до нуля. Если предел превышен, дальше по этой ветке идти не нужно; подходящий лист прибавляет единицу к ответу.

Открыть Kefa and Park на Codeforces ↗

В задачах 893C и 580C могут встретиться цепочки до 100 000 вершин. В Python даже длинные цепочки из задач 115A или 510B могут превысить обычную глубину рекурсии. Для таких случаев используй собственный стек; в задачах на пути и циклы храни в нём также данные о родителе и состоянии обхода.

Категория: Algorithms | Добавил: bzfar77 (Сегодня)
Просмотров: 3 | Теги: dfs, алгоритмы на графах, Python, поиск в глубину | Рейтинг: 0.0/0
Всего комментариев: 0
avatar