mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-27 03:50:19 +08:00
116 lines
8.7 KiB
Markdown
116 lines
8.7 KiB
Markdown
# Обход графа
|
||
|
||
Дерево представляет отношение «один ко многим», в то время как граф обладает большей свободой и может представлять любые отношения «многие ко многим». Таким образом, **мы можем рассматривать дерево как частный случай графа**. Очевидно, что **операция обхода дерева также является частным случаем операции обхода графа**.
|
||
|
||
Как графы, так и деревья требуют применения алгоритмов поиска для выполнения операций обхода. Способы обхода графа также можно разделить на два типа: <u>обход в ширину</u> и <u>обход в глубину</u>.
|
||
|
||
## Обход в ширину
|
||
|
||
**Обход в ширину — это способ обхода от ближайших к дальним вершинам, начиная с некоторой вершины, всегда приоритетно посещая ближайшие вершины и расширяясь слой за слоем**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, сначала обходятся все смежные вершины этой вершины, затем обходятся все смежные вершины следующей вершины и так далее, пока все вершины не будут посещены.
|
||
|
||

|
||
|
||
### Реализация алгоритма
|
||
|
||
BFS обычно реализуется с помощью очереди, код показан ниже. Очередь обладает свойством «первым пришел — первым вышел», что соответствует идее BFS «от ближайших к дальним».
|
||
|
||
1. Добавить начальную вершину обхода `startVet` в очередь и начать цикл.
|
||
2. В каждой итерации цикла извлечь вершину из начала очереди и записать посещение, затем добавить все смежные вершины этой вершины в конец очереди.
|
||
3. Повторять шаг `2.` до тех пор, пока все вершины не будут посещены.
|
||
|
||
Чтобы предотвратить повторный обход вершин, необходимо использовать хеш-множество `visited` для записи того, какие узлы уже были посещены.
|
||
|
||
!!! tip
|
||
|
||
Хеш-множество можно рассматривать как хеш-таблицу, которая хранит только `key`, но не хранит `value`. Оно может выполнять операции добавления, удаления, поиска и изменения `key` за время $O(1)$. Благодаря уникальности `key`, хеш-множество обычно используется для удаления дубликатов данных и в других сценариях.
|
||
|
||
```src
|
||
[file]{graph_bfs}-[class]{}-[func]{graph_bfs}
|
||
```
|
||
|
||
Код довольно абстрактный, рекомендуется сопоставить его с рисунком ниже для более глубокого понимания.
|
||
|
||
=== "<1>"
|
||

|
||
|
||
=== "<2>"
|
||

|
||
|
||
=== "<3>"
|
||

|
||
|
||
=== "<4>"
|
||

|
||
|
||
=== "<5>"
|
||

|
||
|
||
=== "<6>"
|
||

|
||
|
||
=== "<7>"
|
||

|
||
|
||
=== "<8>"
|
||

|
||
|
||
=== "<9>"
|
||

|
||
|
||
=== "<10>"
|
||

|
||
|
||
=== "<11>"
|
||

|
||
|
||
!!! question "Является ли последовательность обхода в ширину уникальной?"
|
||
|
||
Нет. Обход в ширину требует только порядка обхода «от ближайших к дальним», **а порядок обхода нескольких вершин на одинаковом расстоянии может быть произвольным**. Например, на рисунке выше порядок посещения вершин $1$, $3$ может быть изменен, порядок посещения вершин $2$, $4$, $6$ также может быть произвольным.
|
||
|
||
### Анализ сложности
|
||
|
||
**Временная сложность**: Все вершины будут добавлены в очередь и извлечены из нее один раз, что требует $O(|V|)$ времени; в процессе обхода смежных вершин, поскольку граф неориентированный, все ребра будут посещены $2$ раза, что требует $O(2|E|)$ времени; в целом используется $O(|V| + |E|)$ времени.
|
||
|
||
**Пространственная сложность**: Список `res`, хеш-множество `visited`, очередь `que` могут содержать максимум $|V|$ вершин, используя $O(|V|)$ пространства.
|
||
|
||
## Обход в глубину
|
||
|
||
**Обход в глубину — это способ обхода, при котором приоритетно идут до конца, а когда пути нет, возвращаются назад**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, посещается одна из смежных вершин текущей вершины, пока не будет достигнут конец, затем происходит возврат, снова идут до конца и возвращаются, и так далее, пока все вершины не будут обойдены.
|
||
|
||

|
||
|
||
### Реализация алгоритма
|
||
|
||
Эта парадигма алгоритма «идти до конца и возвращаться» обычно реализуется на основе рекурсии. Подобно обходу в ширину, при обходе в глубину также необходимо использовать хеш-множество `visited` для записи посещенных вершин, чтобы избежать повторного посещения вершин.
|
||
|
||
```src
|
||
[file]{graph_dfs}-[class]{}-[func]{graph_dfs}
|
||
```
|
||
|
||
Процесс алгоритма обхода в глубину показан на рисунке ниже.
|
||
|
||
- **Прямая пунктирная линия представляет рекурсию вниз**, указывая на то, что был открыт новый рекурсивный метод для посещения новой вершины.
|
||
- **Изогнутая пунктирная линия представляет возврат назад**, указывая на то, что этот рекурсивный метод уже вернулся, возвращаясь к позиции, где был открыт этот метод.
|
||
|
||
Для более глубокого понимания рекомендуется объединить рисунок ниже с кодом и мысленно смоделировать (или нарисовать) весь процесс DFS, включая то, когда открывается и когда возвращается каждый рекурсивный метод.
|
||
|
||
=== "<1>"
|
||

|
||
|
||
=== "<2>"
|
||

|
||
|
||
=== "<3>"
|
||

|
||
|
||
=== "<4>"
|
||

|
||
|
||
=== "<5>"
|
||

|
||
|
||
=== "<6>"
|
||

|
||
|
||
=== "<7>"
|
||
 |