Menu
Coddy logo textTech

Поиск в глубину (DFS)

Последнее обновление

Поиск в глубину исследует граф, уходя как можно глубже по каждой ветви перед возвратом. Начиная с исходного узла, он посещает соседа, затем соседа этого соседа и так далее, пока не упрётся в тупик (узел без непосещённых соседей); в этот момент он возвращается и пробует следующую ветвь. Нажмите «воспроизвести» выше, чтобы увидеть, как он спускается по одному пути, достигает дна и возвращается обратно.

DFS естественно выражается через стек — либо явный стек (как анимировано здесь), либо стек вызовов через рекурсию. Он посещает каждый узел и ребро один раз, поэтому работает за время O(V + E) для графа с V вершинами и E рёбрами.

Временная и пространственная сложность

МераСложностьПримечания
ВремяO(V + E)Каждая вершина и ребро посещаются один раз
ПамятьO(V)Стек + множество посещённых, в худшем случае все узлы
ОбходСначала самое глубокоеИдёт по одной ветви до конца прежде других
Структура данныхСтек (или рекурсия)Порядок LIFO задаёт поведение в глубину

Пошагово

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

Разобранный пример

Итеративный DFS из A на графе A → [B, C], B → [D], C → [E], помещая соседей в указанном порядке (стек LIFO извлекает последний помещённый первым):

ШагСтек (вершина справа)ПосещённыеДействие
1[A]{}Поместить источник A.
2[B, C]{A}Извлечь A, отметить посещённым, поместить соседей B, затем C.
3[B, E]{A, C}Извлечь C, отметить посещённым, поместить соседа E.
4[B]{A, C, E}Извлечь E, отметить посещённым, соседей нет.
5[D]{A, C, E, B}Извлечь B, отметить посещённым, поместить соседа D.
6[]{A, C, E, B, D}Извлечь D, отметить посещённым, стек пуст: готово.

Когда использовать DFS

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

Чистая, готовая к запуску реализация Depth-First Search на Python, JavaScript, Java, C++, C. Выберите язык, скопируйте код или откройте его уже загруженным в плейграунде Coddy.

Код Depth-First Search на Python

Python
1def dfs(graph, node, visited):2    visited.add(node)3    order = [node]4    for neighbor in graph[node]:5        if neighbor not in visited:6            order.extend(dfs(graph, neighbor, visited))7    return order8
9
10graph = {11    "A": ["B", "C"],12    "B": ["D", "E"],13    "C": ["F"],14    "D": [],15    "E": ["F"],16    "F": [],17}18
19order = dfs(graph, "A", set())20print("DFS order:", " -> ".join(order))
Запустите этот код в плейграунде Python

Частые вопросы о поиске в глубину

Какова временная сложность DFS?
DFS работает за время O(V + E), где V — число вершин, а E — число рёбер, потому что он посещает каждую вершину один раз и рассматривает каждое ребро один раз. Он использует память O(V) для стека и множества посещённых.
В чём разница между DFS и BFS?
DFS использует стек и уходит вглубь по одной ветви перед возвратом, тогда как BFS использует очередь и исследует уровень за уровнем, сначала ближайшие узлы. BFS находит кратчайший путь в невзвешенном графе; DFS — нет, но использует меньше памяти на широких графах и естественен для задач вроде обнаружения циклов и топологической сортировки.
DFS рекурсивный или итеративный?
Работают оба. Рекурсивный вариант неявно использует стек вызовов программы и очень лаконичен; итеративный вариант использует явный стек (как анимация здесь) и избегает переполнения стека из-за глубокой рекурсии на больших графах.
Когда использовать DFS вместо BFS?
Используйте DFS, когда нужно исследовать целые пути: обнаружение циклов, топологическая сортировка, компоненты связности или поиск с возвратом, например решение лабиринта. Он также использует меньше памяти на широких графах, поскольку стек хранит только границу одной ветви за раз. Обращайтесь к BFS, когда нужен кратчайший путь в невзвешенном графе или узлы в порядке расстояния от источника.
Может ли DFS найти кратчайший путь?
Не надёжно. DFS возвращает первый попавшийся путь, который часто не является путём с наименьшим числом рёбер, потому что он предпочитает уходить вглубь, а не расширяться равномерно. Для кратчайших путей в невзвешенном графе используйте BFS, а для взвешенных графов — алгоритм Дейкстры.
Зачем DFS нужно множество посещённых?
Без множества посещённых DFS будет повторно посещать узлы, достижимые несколькими путями, и в циклическом графе зациклится навсегда. Пометка узла посещённым при извлечении (и пропуск уже посещённых извлечений) гарантирует, что каждый узел обрабатывается один раз, и удерживает время работы на уровне O(V + E). Частая ошибка — помечать узлы посещёнными только при извлечении, продолжая помещать дубликаты; это корректно, но может оставлять в стеке устаревшие записи, которые нужно пропускать.
Coddy programming languages illustration

Освойте алгоритмы с Coddy

НАЧАТЬ