DFS (Depth-first search)/Поиск в глубину

FAANG Master
5 min readMay 29, 2023

--

Граф — это совокупность вершин и связей между ними (ребер).

Есть два основных метода обхода графа: Поиск в глубину (dfs/Depth-first search)и Поиск в ширину(bfs/breadth-first search).

Стратегия поиска в глубину заключается в том, чтобы вначале пойти вглубь графа, насколько это возможно. В то время как при поиске в ширину, граф обходится, как бы “по слоям”. Сначала все соседи от начальной вершины, потом соседи соседей и т.д.

Есть две реализации алгоритма поиска в глубину: рекурсивная и через стек.

Временная сложность алгоритма: O(E + V).

Space complexity: O(V).

V — число вершин в графе, E — число ребер.

Реализация через рекурсию:

void dfs(Node root, Set<Node> visited) {
if (root == null) {
return;
}
visit(root);
visited.add(root);
for (Node neighbour : root.getNeighbours()) {
if (!visited.contains(neighbour)) {
dfs(neighbour, visited);
}
}
}

Node — это class вершины графа. Нам необходимо хранить список уже посещенных вершин, чтобы не ходить по одним и тем же вершинам кругами.

visit(Node node) это некий метод или код, который мы хотим выполнить при посещении вершины. Конкретный код будет зависеть от задачи.

Работает алгоритм так: посещаем первую вершину, помечаем ее как посещенную, смотрим на список ее соседей и если мы их еще не посещали, то рекурсивно вызываем функцию уже для соседей.

Реализация при помощи стека:

void dfs(Node root) {
if (root == null) {
return;
}
Set<Node> visited = new HashSet<>();
Stack<Node> stack = new Stack<>();
stack.push(root);
visited.add(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
visit(node);
for (Node neighbour : node.getNeighbours()) {
if (!visited.contains(neighbour)) {
visited.add(neighbour);
stack.push(neighbour);
}
}
}
}

Со стеком. Помещаем первую вершину в стек, помечаем ее как посещенную. Пока стек не пуст: получаем вершину графа из стека, посещаем ее, смотрим на ее соседей, те вершины, которые мы еще не посетили, помечаем их как посещенными и помещаем в стек.

class Node можно реализовать так:

class Node {
private int value;
private final List<Node> neighbours;

public Node(int value, List<Node> neighbours) {
this.value = value;
this.neighbours = neighbours;
}

public List<Node> getNeighbours() {
return this.neighbours;
}

public int getValue() {
return this.value;
}
}

Или, для простоты, на собеседовании можно себе представлять так:

class Node {
int value;
List<Node> neighbours;

public Node(int value, List<Node> neighbours) {
this.value = value;
this.neighbours = neighbours;
}
}

Это позволит обращаться к полям класса:

Node node = ...
node.value //Получить значение
node.neighbours // Получить всех соседей

Давайте посмотрим по шагам как будет работать рекурсивный поиск в глубину на таком графе:

Я буду помечать зеленым цветом уже посещенные вершины (visited):

Шаг 1:

Шаг 2:

Шаг 3:

Шаг 4:

Шаг 5:

Шаг 6:

Шаг 7:

Для реализации через стек, обход будет несколько другой. Он вначале пойдет вправо. Но также, обход будет сначала на максимальную глубину. 1->3->6->2->5->4->7

Посмотрим более подробно на состояние стека и последовательность посещения для реализации DFS через стек. Посещенные вершины также буду помечать зеленым.

Помечаем первую вершину, добавляем ее в стек. В цикле на первой итерации достаем ее из стека и посещаем (visit(node)).

Соседние вершины к 1 это вершины 2 и 3. Они еще не помечены, помещаем их в стек и помечаем.

На следующей итерации цикла while достаем из вершины стека 3 и посещаем ее. Смежные/соседние вершины к 3 это 1 и 6. 1 уже помечена. Поэтому в стек добавляем 6 и помечаем ее.

Достаем из стека 6 и посещаем ее. У 6 только одна смежная вершина — 3. Но она уже помечена. Поэтому переходим на следующую итерацию цикла while.

Достаем из стека 2. Посещаем ее. Смотрим на ее смежные вершины: 1, 4, 5. 1 уже помечена. Поэтому помечаем 4 и 5 и помещаем их в стек.

Достаем из стека 5. Посещаем ее. У 5 одна смежная вершина 2. Но она уже помечена, поэтому ничего не добавляем в стек.

Достаем 4 из стека, посещаем ее. У нее смежные это 2 и 7. 2 уже помечена. Поэтому помечаем 7 и помещаем ее в стек.

Достаем 7 из стека. Посещаем ее. У 7 одна смежная вершина — 4. Она уже помечена. На следующей итерации цикла while стек будет пуст — алгоритм закончен.

--

--

FAANG Master
FAANG Master

Written by FAANG Master

Статьи теперь пишу на https://dev.to/faangmaster, Мой телеграм канал: https://t.me/faangmaster, Мой youtube: https://www.youtube.com/@FAANGMaster

No responses yet