edubun
Русский

Поиск в графе: в ширину и в глубину

В 1736 г. Леонард Эйлер доказал, что нельзя пройти по каждому из семи мостов Кёнигсберга ровно один раз, и с этого ответа началась теория графов.

Точки и линии

Граф представляет собой набор точек, их называют вершинами или узлами, соединённых линиями, их называют рёбрами. Идея простая, и она подходит к огромному числу вещей: города, соединённые дорогами, веб-страницы, соединённые ссылками, люди, связанные дружбой, клетки лабиринта, соединённые там, где нет стены. Если задачу нарисовать в виде графа, на вопросы вроде «можно ли добраться отсюда туда?» и «какой путь самый короткий?» отвечает одна и та же горстка алгоритмов.

КонтекстВ городе Кёнигсберге (теперь Калининград) было семь мостов, которые соединяли два острова и два берега реки. Люди спрашивали, можно ли так прогуляться, чтобы пройти по каждому мосту ровно один раз. В 1736 г. Леонард Эйлер ответил, что нельзя. Его главный шаг состоял в том, чтобы отбросить карту: важны были только участки суши и мосты между ними. Каждый участок суши стал точкой, а каждый мост линией. Затем он показал, что для такой прогулки у каждой точки, кроме, может быть, начала и конца, должно быть чётное число линий, а в Кёнигсберге у всех четырёх участков суши число было нечётным.
A B C D E F
Пример графа в этом уроке. A является началом; F является целью. Рёбра: A‑B, A‑C, B‑D, C‑D, C‑E, D‑F, E‑F.

Как хранить граф в коде

Распространённый способ хранить граф называется списком смежности: для каждой вершины список её соседей. В конспектах курса 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, затем DFS на том же лабиринте и сравни посещённые клетки и длину найденного пути.

Что выбрать

В первом лабиринте лаборатории BFS посещает 105 клеток и находит путь из 22 ходов, самый короткий из возможных. DFS случайно ныряет в сторону цели и посещает всего 31 клетку, но его путь занимает 28 ходов. Ни один не дешевле всегда. Оба обрабатывают каждую вершину и каждое ребро фиксированное число раз, поэтому оба работают за время O(V + E) для V вершин и E рёбер. Выбирай BFS, когда нужно наименьшее число шагов: меньше всего ходов в головоломке, меньше всего связей между двумя людьми. Выбирай DFS, когда нужно исследовать всё или проследить выбор до конца: проверить, связаны ли все комнаты лабиринта, или перебирать ходы головоломки по одной линии и возвращаться, когда застрял. Когда у рёбер разная длина, как у дорог, ни одного из них самого по себе не хватает; методы вроде алгоритма Дейкстры расширяют идею BFS расстояниями.

Оба поиска должны помнить, какие вершины уже видели. Без этого граф с петлёй (из A в B, в D, в C и обратно в A) гонял бы поиск по кругу вечно.
графВершины, соединённые рёбрами
вершина (узел)Точка графа
реброЛиния, соединяющая две вершины
список смежностиДля каждой вершины список её соседей
очередьПервым пришёл, первым ушёл; используется в BFS
BFSПоиск в ширину: кольцо за кольцом; находит пути с наименьшим числом рёбер
DFSПоиск в глубину: сначала вглубь, потом назад; использует стек
O(V + E)Время работы BFS и DFS
  1. Сколько рёбер на кратчайшем пути от A до F в графе из примера?
  2. Сколько рёбер в графе из примера?
  3. Какую структуру данных использует поиск в ширину?
    • Очередь (первым пришёл, первым ушёл)
    • Стек (последним пришёл, первым ушёл)
    • Отсортированный список
    • Никакую
  4. Какой поиск гарантированно находит путь с наименьшим числом рёбер?
    • BFS
    • DFS
    • Оба, всегда
    • Ни один
  5. Расставь вершины в порядке, в котором их посещает BFS из A (соседи берутся по алфавиту).
  6. Расставь вершины в порядке, в котором их посещает рекурсивный DFS из A (соседи по алфавиту).
  7. На сетке лаборатории 15 на 9 без единой стены сколько ходов в кратчайшем пути от S (вверху слева) до G (внизу справа)?
  8. Почему BFS и DFS должны помнить, какие вершины уже видели?
    • Иначе петля в графе заставила бы поиск ходить по кругу вечно
    • Чтобы напечатать их в конце
    • Чтобы их отсортировать
    • Это нужно только для DFS
  9. В каком году Эйлер решил задачу о кёнигсбергских мостах?
    • 1683
    • 1736
    • 1843
    • 1945
  10. Каково время работы BFS на графе с V вершинами и E рёбрами?
    • O(V + E)
    • O(V × E)
    • O(log V)
    • O(2ⱽ)
  11. Ты хочешь узнать наименьшее число связей «друг друга» между двумя людьми в соцсети. Какой поиск выбрать?
    • 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