BFS находит кратчайший путь в невзвешенном графе. DFS удобен для компонент, циклов и топологической сортировки. На бэкенде те же идеи: зависимости сервисов, DAG пайплайнов, обход оргструктур и граф вызовов.
Алгоритмы и структуры данных cs собеседование
Граф — вершины и рёбра. Для сервисов вершина — модуль, таблица, под. Ребро — «зависит от», «зывает», «может перейти».
Обычное представление — список смежности: для каждой вершины список соседей. Память O(V+E). Обойти соседей дёшево. Матрица смежности V×V имеет смысл на плотных графах и когда нужна проверка ребра за O(1).
Пример. Карта метро без весов рёбер. BFS найдёт путь с наименьшим числом пересадок: как круги по воде, сначала все соседи, потом соседи соседей. DFS залезет в одну ветку до конца и не обещает кратчайший путь.
BFS идёт вширь: очередь. Первый раз, когда вы пришли в вершину в невзвешенном графе, путь уже кратчайший по числу рёбер.
DFS идёт вглубь: стек или рекурсия. Кратчайший путь не обещает. Зато хорошо ловит циклы (цвета вершин) и строит топологический порядок на DAG — направленном графе без циклов.
Рекурсивный DFS на глубоком графе в Go опасен для стека горутины. Глубокая цепочка зависимостей или цикл без visited упрётся в лимит. Явный стек безопаснее. Без множества visited оба обхода на циклическом графе не закончатся.
BFS и DFS
Сложность обоих на списке смежности — O(V+E). Каждую вершину и каждое ребро смотрят константное число раз, если маркируют посещённое. Это не O(V²), пока вы не работаете матрицей на разреженном графе и не перебираете все пары.
func shortest(adj [][]int, start, goal int) int {
dist := make([]int, len(adj))
for i := range dist {
dist[i] = -1
}
dist[start] = 0
q := []int{start}
for len(q) > 0 {
v := q[0]
q = q[1:]
if v == goal {
return dist[v]
}
for _, w := range adj[v] {
if dist[w] == -1 {
dist[w] = dist[v] + 1
q = append(q, w)
}
}
}
return -1
}
// DFS цвета: 0 белый, 1 серый (в стеке), 2 чёрный
// ребро в серую вершину = цикл в направленном графе
BFS: кратчайший путь в невзвешенном графе. DFS с цветами: цикл в зависимостях.
Типичные задачи
Цикл в зависимостях модулей или миграций — направленный граф и DFS с тремя цветами. Серая вершина в стеке вызова означает: мы вернулись к предку. BFS тоже обойдёт граф, но классический ответ на цикл в DAG — раскраска DFS или отслеживание «в текущем пути».
- Кратчайший путь в сетке без весов — BFS. Если есть рёбра веса 0 и 1 — 0-1 BFS.
- Dijkstra — неотрицательные веса. С отрицательными — Bellman–Ford, не «просто Dijkstra».
- Топологическая сортировка — только DAG: порядок сборки, порядок миграций, порядок инициализации сервисов.
- Компоненты связности — DFS или BFS от каждой не посещённой вершины.
Зачем это бэкенду
Микросервисы — граф вызовов. Цикл A→B→A на синхронных HTTP — скрытый deadlock по таймаутам. Пайплайн CI — DAG. Иерархия отделов — дерево или DAG, не полный граф. Обход «все подчинённые» — BFS/DFS по списку смежности, не N+1 SQL в цикле, если заранее собрать рёбра.
Пример. Сервис заказов зовёт склад, склад зовёт заказы. Без таймаутов оба ждут друг друга. DFS с цветами на графе зависимостей находит такой цикл ещё на картинке, не в три часа ночи.
Типичная ошибка
Говорить «кратчайший путь — всегда Dijkstra» на невзвешенном графе: BFS проще и достаточен. И забывать visited: на цикле DFS/BFS без маркировки — бесконечный обход. Рекурсия DFS на широком прод-графе — ещё и риск стека.
На собеседовании
BFS — очередь и кратчайший путь в невзвешенном графе. DFS — циклы и топосорт. Сложность O(V+E) на списке смежности. Без visited обход на цикле не закончится. Граф вызовов микросервисов — тот же язык.