1. Обзор
В этом руководстве мы рассмотрим поиск в глубину в Java.
Поиск в глубину (DFS) — это алгоритм обхода, используемый как для структур данных Tree, так и для Graph . Поиск в глубину углубляется в каждую ветвь, прежде чем переходить к изучению другой ветви .
В следующих разделах мы сначала рассмотрим реализацию дерева, а затем графа.
Чтобы узнать, как реализовать эти структуры в Java, ознакомьтесь с нашими предыдущими руководствами по двоичному дереву и графику .
2. Поиск в глубину дерева
Существует три разных порядка обхода дерева с помощью DFS:
- Обход предзаказа
- Неупорядоченный обход
- Почтовый обход
2.1. Обход предзаказа
При обходе в прямом порядке мы сначала обходим корень, затем левое и правое поддеревья.
Мы можем просто реализовать предварительный обход с помощью рекурсии :
- Посетить
текущий
узел - Обход
левого
поддерева - Обход
правого
поддерева
public void traversePreOrder(Node node) {
if (node != null) {
visit(node.value);
traversePreOrder(node.left);
traversePreOrder(node.right);
}
}
Мы также можем реализовать предварительный обход без рекурсии.
Чтобы реализовать итеративный обход предварительного заказа, нам понадобится Stack
, и мы выполним следующие шаги:
Вставьте
root
в нашстек
Пока
стек
не пустВытолкнуть
текущий
узелПосетить
текущий
узелНажмите
правый
дочерний элемент, затемлевый
дочерний элемент, чтобысложить
public void traversePreOrderWithoutRecursion() {
Stack<Node> stack = new Stack<Node>();
Node current = root;
stack.push(root);
while(!stack.isEmpty()) {
current = stack.pop();
visit(current.value);
if(current.right != null) {
stack.push(current.right);
}
if(current.left != null) {
stack.push(current.left);
}
}
}
2.2. Неупорядоченный обход
Для неупорядоченного обхода мы сначала обходим левое поддерево, затем корень и, наконец, правое поддерево .
Неупорядоченный обход для бинарного дерева поиска означает обход узлов в порядке возрастания их значений.
Мы можем просто реализовать неупорядоченный обход с помощью рекурсии:
public void traverseInOrder(Node node) {
if (node != null) {
traverseInOrder(node.left);
visit(node.value);
traverseInOrder(node.right);
}
}
Мы также можем реализовать неупорядоченный обход без рекурсии :
Инициализировать
текущий
узел с помощьюroot
Пока
текущий
не равен нулю илистек
не пустПродолжайте помещать
левый
дочерний элемент встек,
пока мы не достигнем самого левого дочернего элементатекущего
узла.Поп и посетите самый левый узел из
стека
Установите
текущий
правый
дочерний элемент выдвинутого узла
public void traverseInOrderWithoutRecursion() {
Stack stack = new Stack<>();
Node current = root;
while (current != null || !stack.isEmpty()) {
while (current != null) {
stack.push(current);
current = current.left;
}
Node top = stack.pop();
visit(top.value);
current = top.right;
}
}
2.3. Почтовый обход
Наконец, при обходе в обратном порядке мы проходим левое и правое поддерево до того, как проходим корень .
Мы можем следовать нашему предыдущему рекурсивному решению :
public void traversePostOrder(Node node) {
if (node != null) {
traversePostOrder(node.left);
traversePostOrder(node.right);
visit(node.value);
}
}
Или мы также можем реализовать обратный обход без рекурсии :
Поместить
корневой
узелв
стек
``Пока
стек
не пустПроверяем, прошли ли мы уже левое и правое поддерево
Если нет, то поместите
правый
дочерний элемент илевый
дочерний элемент встек
public void traversePostOrderWithoutRecursion() {
Stack<Node> stack = new Stack<Node>();
Node prev = root;
Node current = root;
stack.push(root);
while (!stack.isEmpty()) {
current = stack.peek();
boolean hasChild = (current.left != null || current.right != null);
boolean isPrevLastChild = (prev == current.right ||
(prev == current.left && current.right == null));
if (!hasChild || isPrevLastChild) {
current = stack.pop();
visit(current.value);
prev = current;
} else {
if (current.right != null) {
stack.push(current.right);
}
if (current.left != null) {
stack.push(current.left);
}
}
}
}
3. Графический поиск в глубину
Основное различие между графами и деревьями состоит в том, что графы могут содержать циклы .
Поэтому, чтобы избежать циклического поиска, мы будем отмечать каждый узел при его посещении.
Мы увидим две реализации DFS графа, с рекурсией и без рекурсии.
3.1. График DFS с рекурсией
Во-первых, давайте начнем с рекурсии:
- Мы начнем с заданного узла
- Отметить
текущий
узел как посещенный - Посетить
текущий
узел - Обход непосещенных смежных вершин
public void dfs(int start) {
boolean[] isVisited = new boolean[adjVertices.size()];
dfsRecursive(start, isVisited);
}
private void dfsRecursive(int current, boolean[] isVisited) {
isVisited[current] = true;
visit(current);
for (int dest : adjVertices.get(current)) {
if (!isVisited[dest])
dfsRecursive(dest, isVisited);
}
}
3.2. График DFS без рекурсии
Мы также можем реализовать граф DFS без рекурсии. Мы просто будем использовать стек
:
Мы начнем с заданного узла
Поместить
начальный
узел встек
Пока
стек
не пустОтметить
текущий
узел как посещенныйПосетить
текущий
узелВтолкнуть непосещенные соседние вершины
public void dfsWithoutRecursion(int start) {
Stack<Integer> stack = new Stack<Integer>();
boolean[] isVisited = new boolean[adjVertices.size()];
stack.push(start);
while (!stack.isEmpty()) {
int current = stack.pop();
if(!isVisited[current]){
isVisited[current] = true;
visit(current);
for (int dest : adjVertices.get(current)) {
if (!isVisited[dest])
stack.push(dest);
}
}
}
3.4. Топологическая сортировка
Существует множество приложений для поиска по графу в глубину. Одним из известных приложений для DFS является топологическая сортировка.
Топологическая сортировка ориентированного графа — это линейное упорядочение его вершин таким образом, что для каждого ребра исходный узел предшествует целевому.
Чтобы получить топологическую сортировку, нам понадобится простое дополнение к DFS, которую мы только что реализовали:
- Нам нужно хранить посещенные вершины в стеке, потому что топологическая сортировка — это посещенные вершины в обратном порядке.
- Мы помещаем посещенный узел в стек только после обхода всех его соседей.
public List<Integer> topologicalSort(int start) {
LinkedList<Integer> result = new LinkedList<Integer>();
boolean[] isVisited = new boolean[adjVertices.size()];
topologicalSortRecursive(start, isVisited, result);
return result;
}
private void topologicalSortRecursive(int current, boolean[] isVisited, LinkedList<Integer> result) {
isVisited[current] = true;
for (int dest : adjVertices.get(current)) {
if (!isVisited[dest])
topologicalSortRecursive(dest, isVisited, result);
}
result.addFirst(current);
}
4. Вывод
В этой статье мы обсудили поиск в глубину как для структур данных Tree, так и для Graph.
Полный исходный код доступен на GitHub .