# Обход графа Дерево представляет отношение «один ко многим», тогда как граф обладает большей свободой и может выражать произвольные отношения «многие ко многим». Поэтому дерево можно рассматривать как частный случай графа. Очевидно, что **операции обхода дерева также являются частным случаем операций обхода графа**. И графы, и деревья требуют применения алгоритмов обхода. Способы обхода графа также делятся на два типа: обход в ширину и обход в глубину. ## Обход в ширину **Обход в ширину - это способ обхода от ближнего к дальнему, при котором начиная с некоторого узла сначала посещают ближайшие вершины, а затем слой за слоем расширяются наружу**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, мы сначала обходим все смежные вершины этой вершины, затем все смежные вершины следующей вершины и так далее, пока не будут посещены все вершины. ![Обход графа в ширину](graph_traversal.assets/graph_bfs.png) ### Реализация алгоритма BFS обычно реализуется с помощью очереди, код приведен ниже. Очередь обладает свойством «первым пришел - первым вышел», что хорошо соответствует идее BFS «от ближнего к дальнему». 1. Поместить стартовую вершину обхода `startVet` в очередь и запустить цикл. 2. На каждой итерации цикла извлекать вершину из головы очереди и записывать ее посещение, после чего добавлять все смежные вершины этой вершины в хвост очереди. 3. Повторять шаг `2.` до тех пор, пока не будут посещены все вершины. Чтобы предотвратить повторный обход вершин, нам нужно хеш-множество `visited` , в котором записывается, какие вершины уже посещены. !!! tip Хеш-множество можно рассматривать как хеш-таблицу, которая хранит только `key` и не хранит `value` . Оно позволяет выполнять добавление, удаление и проверку наличия `key` за $O(1)$ времени. Благодаря уникальности `key` хеш-множество обычно используется, например, для устранения повторов. ```src [file]{graph_bfs}-[class]{}-[func]{graph_bfs} ``` Код сравнительно абстрактен, поэтому для лучшего понимания рекомендуется сопоставлять его с тем, что показано на рисунке ниже. === "<1>" ![Шаги обхода графа в ширину](graph_traversal.assets/graph_bfs_step1.png) === "<2>" ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) === "<3>" ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) === "<4>" ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) === "<5>" ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) === "<6>" ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) === "<7>" ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) === "<8>" ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) === "<9>" ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) === "<10>" ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) === "<11>" ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) !!! question "Является ли последовательность обхода в ширину единственной?" Нет. Обход в ширину требует только соблюдения порядка «от ближнего к дальнему», **а порядок обхода нескольких вершин на одинаковом расстоянии может произвольно меняться**. Например, на рисунке выше можно поменять местами порядок посещения вершин $1$ и $3$ , а вершины $2$, $4$, $6$ также можно переставлять произвольно. ### Анализ сложности **Временная сложность**: все вершины по одному разу помещаются в очередь и извлекаются из нее, что требует $O(|V|)$ времени. При обходе смежных вершин, поскольку граф неориентированный, все ребра будут посещены по $2$ раза, что требует $O(2|E|)$ времени. В сумме получается $O(|V| + |E|)$ . **Пространственная сложность**: список `res` , хеш-множество `visited` и очередь `que` в худшем случае могут содержать до $|V|$ вершин, поэтому требуется $O(|V|)$ памяти. ## Обход в глубину **Обход в глубину - это способ обхода, при котором сначала идут до самого конца, а когда дальше идти нельзя, возвращаются назад**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, мы выбираем некоторую смежную вершину текущей вершины, идем до упора, затем возвращаемся назад, снова идем до упора и так далее, пока не будут посещены все вершины. ![Обход графа в глубину](graph_traversal.assets/graph_dfs.png) ### Реализация алгоритма Такой алгоритмический шаблон «дойти до конца и вернуться» обычно реализуется через рекурсию. Подобно обходу в ширину, в обходе в глубину мы также используем хеш-множество `visited` для записи уже посещенных вершин и тем самым избегаем повторного посещения. ```src [file]{graph_dfs}-[class]{}-[func]{graph_dfs} ``` Алгоритмический процесс обхода в глубину показан на рисунке ниже. - **Прямая пунктирная линия обозначает нисходящую рекурсию** , то есть запуск нового рекурсивного метода для посещения новой вершины. - **Изогнутая пунктирная линия обозначает восходящую рекурсию** , то есть данный рекурсивный метод завершился и управление вернулось туда, откуда он был вызван. Чтобы лучше понять алгоритм, рекомендуется сопоставить код с тем, что показано на рисунке ниже, и мысленно проследить весь процесс DFS, включая моменты запуска и возврата каждого рекурсивного вызова. === "<1>" ![Шаги обхода графа в глубину](graph_traversal.assets/graph_dfs_step1.png) === "<2>" ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) === "<3>" ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) === "<4>" ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) === "<5>" ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) === "<6>" ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) === "<7>" ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) === "<8>" ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) === "<9>" ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) === "<10>" ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) === "<11>" ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) !!! question "Является ли последовательность обхода в глубину единственной?" Как и в случае обхода в ширину, последовательность DFS тоже не является единственной. Для заданной вершины допустимо сначала углубиться в любое направление, то есть порядок смежных вершин может быть произвольным, и все такие варианты будут корректными обходами в глубину. Если взять в качестве примера обход дерева, то варианты «корень $\rightarrow$ лево $\rightarrow$ право», «лево $\rightarrow$ корень $\rightarrow$ право» и «лево $\rightarrow$ право $\rightarrow$ корень» соответствуют прямому, симметричному и обратному обходам соответственно. Они показывают три разных приоритета обхода, но все они относятся к обходу в глубину. ### Анализ сложности **Временная сложность**: все вершины будут посещены по $1$ разу, что требует $O(|V|)$ времени. Все ребра будут посещены по $2$ раза, что требует $O(2|E|)$ времени. Суммарно получается $O(|V| + |E|)$ . **Пространственная сложность**: число вершин в списке `res` и хеш-множестве `visited` в худшем случае достигает $|V|$ , максимальная глубина рекурсии тоже равна $|V|$ , поэтому требуется $O(|V|)$ памяти.