3.1.2 — два способа посетить каждую вершину ровно один раз3.1.2 — two ways to visit every vertex exactly once
BFS и DFS — не два случайных алгоритма, а два разных порядка систематического обхода графа, каждый из которых открывает свой набор свойств графа. Почти любой более сложный графовый алгоритм в этом разделе (кратчайшие пути, компоненты связности, топологическая сортировка) — это BFS или DFS с дополнительной бухгалтерией.BFS and DFS aren't two arbitrary algorithms — they're two different systematic orders of visiting a graph, each exposing a different set of graph properties. Almost every more advanced graph algorithm in this section (shortest paths, connected components, topological sort) is BFS or DFS with extra bookkeeping bolted on.
BFS посещает граф "слоями": сначала все вершины на расстоянии 1 ребра от старта, потом все на расстоянии 2, и так далее. Реализуется через очередь (FIFO): вершина добавляется в очередь при первом обнаружении, обрабатывается (посещаются её соседи) в порядке добавления. Ключевое следствие этого порядка: когда BFS впервые достигает вершину, найденный путь до неё — гарантированно кратчайший по числу рёбер (не по весу — для взвешенного графа это уже Дейкстра, см. 3.2.1).BFS visits a graph "layer by layer": first every vertex 1 edge away from the start, then every vertex 2 edges away, and so on. It's implemented with a queue (FIFO): a vertex is enqueued the first time it's discovered, and processed (its neighbors are visited) in the order it was enqueued. The key consequence of this order: the first time BFS reaches a vertex, the path found to it is guaranteed to be the shortest by edge count (not by weight — that's Dijkstra for a weighted graph, see 3.2.1).
BFS от S: посещает A,B (1 ребро), затем C,D,E (2 ребра) — строго по слоямBFS from S: visits A,B (1 edge away), then C,D,E (2 edges away) — strictly layer by layer
std::vector<int> bfs(const std::vector<std::vector<int>>& adj, int start) {
std::vector<int> dist(adj.size(), -1);
std::queue<int> q;
dist[start] = 0;
q.push(start);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : adj[u]) {
if (dist[v] == -1) { // впервые обнаружена — кратчайшая по рёбрам// first discovery — shortest by edge count
dist[v] = dist[u] + 1;
q.push(v);
}
}
}
return dist;
}
DFS уходит из текущей вершины в первого непосещённого соседа максимально глубоко, прежде чем вернуться (backtrack) и попробовать следующую ветку. Реализуется через стек — явный, либо неявный через рекурсию (что и есть стек вызовов). В отличие от BFS, DFS не даёт кратчайших путей, зато естественно выявляет структуру графа: порядок входа/выхода из вершины (`tin`/`tout`) — основа алгоритмов поиска мостов, точек сочленения и компонент сильной связности (см. 3.1.3).DFS goes as deep as possible into the first unvisited neighbor before backtracking to try the next branch. It's implemented with a stack — explicit, or implicit via recursion (which is exactly a call stack). Unlike BFS, DFS doesn't give shortest paths, but it naturally exposes the graph's structure: a vertex's entry/exit order (`tin`/`tout`) underlies the algorithms for finding bridges, articulation points, and strongly connected components (see 3.1.3).
DFS уходит максимально глубоко (S→A→B), прежде чем откатиться — A→C образует back edge, а не tree edgeDFS goes as deep as possible (S→A→B) before backtracking — A→C forms a back edge, not a tree edge
void dfs(int u, const std::vector<std::vector<int>>& adj, std::vector<bool>& visited) {
visited[u] = true;
for (int v : adj[u]) {
if (!visited[v]) dfs(v, adj, visited); // углубляемся до упора// go as deep as possible
}
}
Классификация рёбер, которую даёт DFS на ориентированном графе, — не академическая формальность, а инструмент: tree edge (ведёт к непосещённой вершине — часть DFS-дерева), back edge (ведёт к предку в текущей рекурсии — наличие back edge означает, что в графе есть цикл), forward edge (ведёт к уже полностью обработанному потомку), cross edge (ведёт в не связанную по предкам часть дерева). Обнаружение цикла в ориентированном графе — это буквально проверка "встретился ли back edge" во время DFS.The edge classification DFS gives on a directed graph isn't academic formality, it's a tool: a tree edge (leads to an unvisited vertex — part of the DFS tree), a back edge (leads to an ancestor in the current recursion — a back edge existing means the graph has a cycle), a forward edge (leads to an already-fully-processed descendant), a cross edge (leads to an unrelated part of the tree). Detecting a cycle in a directed graph is literally checking "was a back edge encountered" during DFS.
Мост (bridge) — ребро, удаление которого увеличивает число компонент связности графа (разрывает единственный путь между двумя частями). Точка сочленения (articulation point / cut vertex) — то же самое, но про вершину. Оба находятся одним проходом DFS с использованием `tin[v]` (время входа в v) и `low[v]` (минимальный tin, достижимый из поддерева v через один back edge) — если для ребра (u,v) верно `low[v] > tin[u]`, поддерево v не может достичь u или выше без этого ребра, значит (u,v) — мост.A bridge is an edge whose removal increases the graph's number of connected components (it breaks the only path between two parts). An articulation point (cut vertex) is the same idea for a vertex. Both are found in a single DFS pass using `tin[v]` (v's entry time) and `low[v]` (the minimum tin reachable from v's subtree via one back edge) — if for edge (u,v) it holds that `low[v] > tin[u]`, v's subtree cannot reach u or above without that edge, meaning (u,v) is a bridge.
"Люди, которых вы можете знать" — типичная фича на BFS: обход на 2 слоя от пользователя (друзья друзей), исключая уже существующих друзей. Гарантия BFS "ближайшее по рёбрам находится первым" здесь буквально означает "самые близкие по социальному графу кандидаты показываются в первую очередь"."People you may know" is a typical BFS feature: a 2-layer traversal from the user (friends of friends), excluding existing friends. BFS's guarantee that "the closest by edge count is found first" here literally means "the closest candidates by social graph are surfaced first".
Каждое состояние головоломки — вершина, каждый допустимый ход — ребро без веса. BFS от начального состояния до целевого находит решение с минимальным числом ходов — то есть именно то, что называют "оптимальным решением" в контексте головоломок.Each puzzle state is a vertex, each legal move is an unweighted edge. BFS from the start state to the goal state finds the solution with the fewest moves — exactly what's meant by an "optimal solution" in a puzzle context.
`find`, индексация файлов поисковиком, рекурсивное копирование директории — все уходят в каждую поддиректорию максимально глубоко, прежде чем перейти к следующей соседней папке. Это ровно DFS на дереве (частный случай графа без циклов).`find`, a search engine's file indexer, recursive directory copying — all of them descend into each subdirectory as deep as possible before moving to the next sibling folder. That's exactly DFS on a tree (a graph special-cased to have no cycles).
Граф ожидания (wait-for graph) в СУБД: транзакция A ждёт ресурс, занятый транзакцией B — ребро A→B. Дедлок — это цикл в этом графе. СУБД периодически запускают DFS именно для обнаружения такого цикла и принудительного отката одной из транзакций.A DBMS's wait-for graph: transaction A waits on a resource held by transaction B — an edge A→B. A deadlock is a cycle in this graph. DBMSes periodically run DFS specifically to detect such a cycle and force a rollback of one of the transactions.
Дата-центры соединены каналами связи (граф) — мост в этом графе означает единственный канал, чьё падение разрывает связность сети на две изолированные части (single point of failure). Сетевые инженеры буквально ищут мосты и точки сочленения перед тем, как утверждать топологию сети как отказоустойчивую.Data centers connected by network links (a graph) — a bridge in this graph is a single link whose failure splits the network into two isolated parts (a single point of failure). Network engineers literally search for bridges and articulation points before signing off on a network topology as fault-tolerant.
DFS-рекурсия на графе с длинной цепочкой (например, связный список из 10⁶ вершин) создаёт 10⁶ вложенных вызовов — стандартный стек потока (обычно 1-8 МБ) переполняется задолго до этого. Продакшен-реализации на больших графах используют явный стек (std::vector как стек) вместо рекурсии именно по этой причине.DFS recursion on a graph with a long chain (say, a linked-list-shaped graph of 10⁶ vertices) creates 10⁶ nested calls — a standard thread stack (typically 1-8 MB) overflows long before that. Production implementations on large graphs use an explicit stack (a std::vector used as a stack) instead of recursion for exactly this reason.
Запуск BFS/DFS один раз от произвольной стартовой вершины посещает только её компоненту связности. Если задача требует обработать весь граф (например, посчитать все компоненты связности), нужен внешний цикл по всем вершинам, запускающий обход из каждой ещё не посещённой — иначе часть графа молча игнорируется.Running BFS/DFS once from an arbitrary start vertex only visits its connected component. If the task requires processing the entire graph (say, counting all connected components), an outer loop over all vertices is needed, launching a traversal from each one not yet visited — otherwise part of the graph is silently ignored.
BFS даёт кратчайший путь по числу рёбер — если рёбра имеют разный вес, путь с меньшим числом рёбер вполне может быть длиннее (дороже) пути с большим числом рёбер. Применение BFS напрямую к взвешенному графу вместо Дейкстры/Беллмана-Форда (3.2.1) — одна из самых частых логических ошибок, которая не падает с исключением, а просто даёт неверный ответ.BFS gives the shortest path by edge count — if edges carry different weights, a path with fewer edges can easily be longer (more expensive) than one with more edges. Applying BFS directly to a weighted graph instead of Dijkstra/Bellman-Ford (3.2.1) is one of the most common logic errors — it doesn't throw an exception, it just silently gives the wrong answer.