Поиск в графе: в ширину и в глубину
В 1736 г. Леонард Эйлер доказал, что нельзя пройти по каждому из семи мостов Кёнигсберга ровно один раз, и с этого ответа началась теория графов.
Точки и линии
Граф представляет собой набор точек, их называют вершинами или узлами, соединённых линиями, их называют рёбрами. Идея простая, и она подходит к огромному числу вещей: города, соединённые дорогами, веб-страницы, соединённые ссылками, люди, связанные дружбой, клетки лабиринта, соединённые там, где нет стены. Если задачу нарисовать в виде графа, на вопросы вроде «можно ли добраться отсюда туда?» и «какой путь самый короткий?» отвечает одна и та же горстка алгоритмов.
Как хранить граф в коде
Распространённый способ хранить граф называется списком смежности: для каждой вершины список её соседей. В конспектах курса MIT 6.006 используется именно это, массив или словарь Adj, где Adj[u] перечисляет вершины, соединённые с u. Для графа выше это выглядит так:
const graph = {
A: ["B", "C"],
B: ["A", "D"],
C: ["A", "D", "E"],
D: ["B", "C", "F"],
E: ["C", "F"],
F: ["D", "E"],
};Поиск в ширину: кольцо за кольцом
Поиск в ширину (BFS) исследует граф кольцами вокруг начала. Сначала само начало, на расстоянии 0. Затем все его соседи, на расстоянии 1. Затем каждая новая вершина рядом с ними, на расстоянии 2, и так далее. Для этого он держит очередь, ряд, в котором первым выходит тот, кто первым вошёл. Возьми вершину в начале очереди, посети её и добавь её непросмотренных соседей в конец. Из A в примере BFS посещает A, затем B и C, затем D и E, затем F. Поскольку он движется наружу по одному кольцу, то, впервые достигнув вершины, BFS уже нашёл путь с наименьшим числом рёбер. F находится в 3 рёбрах от A, например A, B, D, F.
function bfs(graph, start) {
const seen = new Set([start]);
const queue = [start];
const order = [];
while (queue.length) {
const u = queue.shift(); // первым пришёл, первым ушёл
order.push(u);
for (const v of graph[u]) {
if (!seen.has(v)) { seen.add(v); queue.push(v); }
}
}
return order; // A B C D E F
}Поиск в глубину: как можно дальше, потом назад
Поиск в глубину (DFS) ведёт себя как человек в лабиринте, который держится одной рукой за стену. Он идёт по одному пути так глубоко, как только можно. Дойдя до тупика или до места, где уже был, он возвращается к последней развилке и пробует следующий вариант. Он использует стек вместо очереди, последним пришёл, первым ушёл, и его очень естественно писать рекурсией, где запоминанием занимается стек вызовов. Из A рекурсивный DFS посещает A, B, D, C, E, F. DFS достигает всех вершин, которых достигает BFS, но путь, по которому он идёт, не обязательно самый короткий.
function dfs(graph, u, seen = new Set(), order = []) {
seen.add(u);
order.push(u);
for (const v of graph[u]) if (!seen.has(v)) dfs(graph, v, seen, order);
return order; // A B D C E F
}Перетащи, чтобы повернуть. Колёсико или щипок для масштаба.
Что выбрать
В первом лабиринте лаборатории BFS посещает 105 клеток и находит путь из 22 ходов, самый короткий из возможных. DFS случайно ныряет в сторону цели и посещает всего 31 клетку, но его путь занимает 28 ходов. Ни один не дешевле всегда. Оба обрабатывают каждую вершину и каждое ребро фиксированное число раз, поэтому оба работают за время O(V + E) для V вершин и E рёбер. Выбирай BFS, когда нужно наименьшее число шагов: меньше всего ходов в головоломке, меньше всего связей между двумя людьми. Выбирай DFS, когда нужно исследовать всё или проследить выбор до конца: проверить, связаны ли все комнаты лабиринта, или перебирать ходы головоломки по одной линии и возвращаться, когда застрял. Когда у рёбер разная длина, как у дорог, ни одного из них самого по себе не хватает; методы вроде алгоритма Дейкстры расширяют идею BFS расстояниями.
| граф | Вершины, соединённые рёбрами |
| вершина (узел) | Точка графа |
| ребро | Линия, соединяющая две вершины |
| список смежности | Для каждой вершины список её соседей |
| очередь | Первым пришёл, первым ушёл; используется в BFS |
| BFS | Поиск в ширину: кольцо за кольцом; находит пути с наименьшим числом рёбер |
| DFS | Поиск в глубину: сначала вглубь, потом назад; использует стек |
| O(V + E) | Время работы BFS и DFS |
- Сколько рёбер на кратчайшем пути от A до F в графе из примера?
- Сколько рёбер в графе из примера?
- Какую структуру данных использует поиск в ширину?
- Очередь (первым пришёл, первым ушёл)
- Стек (последним пришёл, первым ушёл)
- Отсортированный список
- Никакую
- Какой поиск гарантированно находит путь с наименьшим числом рёбер?
- BFS
- DFS
- Оба, всегда
- Ни один
- Расставь вершины в порядке, в котором их посещает BFS из A (соседи берутся по алфавиту).
- Расставь вершины в порядке, в котором их посещает рекурсивный DFS из A (соседи по алфавиту).
- На сетке лаборатории 15 на 9 без единой стены сколько ходов в кратчайшем пути от S (вверху слева) до G (внизу справа)?
- Почему BFS и DFS должны помнить, какие вершины уже видели?
- Иначе петля в графе заставила бы поиск ходить по кругу вечно
- Чтобы напечатать их в конце
- Чтобы их отсортировать
- Это нужно только для DFS
- В каком году Эйлер решил задачу о кёнигсбергских мостах?
- 1683
- 1736
- 1843
- 1945
- Каково время работы BFS на графе с V вершинами и E рёбрами?
- O(V + E)
- O(V × E)
- O(log V)
- O(2ⱽ)
- Ты хочешь узнать наименьшее число связей «друг друга» между двумя людьми в соцсети. Какой поиск выбрать?
- BFS
- DFS
- Двоичный поиск
- Сортировка слиянием
- Граф состоит из вершин, соединённых рёбрами; хранят его в списках смежности.
- BFS использует очередь, идёт кольцо за кольцом и находит пути с наименьшим числом рёбер.
- DFS использует стек (или рекурсию), сначала идёт вглубь, потом возвращается.
- Оба работают за O(V + E) и должны помнить просмотренные вершины.
- Решение Эйлера о кёнигсбергских мостах 1736 г. положило начало теории графов.
Источники курса (11)
- MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
- lecture notes 1 Introduction, 3 Sorting, 9 Breadth-First Search, 10 Depth-First Search (ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes) and Fall 2011 lecture 13 BFS notes (ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011)
- Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (MIT Press)
- with the CLRS-based Khan Academy algorithms unit by Thomas Cormen and Devin Balkcom of Dartmouth (khanacademy.org/computing/computer-science/algorithms: binary search, selection sort analysis, merge sort, quicksort overview and analysis)
- MacTutor History of Mathematics, Al-Khwarizmi (mathshistory.st-andrews.ac.uk/Biographies/Al-Khwarizmi: word algorithm from the Latin title Algoritmi de numero Indorum)
- D. E. Joyce, Euclid's Elements Book VII Propositions 1 and 2 (mathcs.clarku.edu/~djoyce/elements/bookVII)
- Wolfram MathWorld, Merge Sort (mathworld.wolfram.com/MergeSort.html: proposed by John von Neumann in 1945, after Knuth 1998 p. 159)
- Stanford CS "Tony Hoare: Quicksort" (cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/quicksort.html: devised while a student at Moscow State University for Russian-English machine translation with a tape dictionary, programmed in Algol 60 and published 1961; the year 1959 from the Wikipedia Quicksort article and its cited sources)
- Bodleian Libraries, Ada Lovelace (blogs.bodleian.ox.ac.uk/adalovelace: 1843 paper, Note G Bernoulli numbers)
- Fourmilab text of Menabrea's Sketch with Lovelace's notes (fourmilab.ch/babbage/sketch.html)
- Seven Bridges of Konigsberg (Euler 1736) and Tower of Hanoi (Edouard Lucas, 1883) checked against the Wikipedia articles and their cited sources.
Проверено: 2026-10-17