Признак в условии
- вложенная структура: дерево, вложенные объекты, матрица
- «обойти всё», «есть ли путь», «связные области»
- задача естественно разбивается на такую же задачу поменьше
Шаблон
function walk(node, state) {
if (!node) return база;
if (посещён(node)) return;
пометить(node);
for (const next of соседи(node)) walk(next, state);
}
Для матрицы проверку границ делают в начале функции, а не перед каждым вызовом:
const fill = (r, c) => {
if (r < 0 || c < 0 || r >= rows || c >= cols) return;
if (grid[r][c] !== искомое) return;
grid[r][c] = помечено;
fill(r + 1, c); fill(r - 1, c); fill(r, c + 1); fill(r, c - 1);
};
Ориентированный граф - три цвета
Двух состояний мало: нужно отличать «в текущем пути» (серый) от «обработан» (чёрный). Ребро в серую вершину - цикл.
Сложность
Время O(V + E), память O(глубины) на стек вызовов.
Частые ошибки
- нет защиты от повторного посещения - зацикливание на графе с циклами
- два состояния вместо трёх при поиске цикла: ромб ошибочно объявляется циклическим
- глубокая рекурсия переполняет стек - на больших данных нужен явный стек
- при проверке BST сравнивают только с непосредственным родителем
Мнемоника
«Вглубь до упора, потом назад»
Не путать с
- Обход в ширину - в глубину - когда нужен любой путь, все пути или структура (компоненты, циклы); в ширину - когда нужен кратчайший
- Перебор с возвратом - перебор с возвратом - это обход в глубину по дереву решений с отменой выбора; если отменять нечего, это просто обход
Задачи на этот паттерн
- глубина дерева
- плоский список узлов дерева
- число островов
- проверка двоичного дерева поиска
- зеркальное отражение дерева
- ближайший общий предок
- есть ли путь с суммой
- сериализация дерева
- цикл в ориентированном графе
- заливка области
- одинаковые ли деревья
- зеркальность дерева
- диаметр дерева
- число связных групп