--- comments: true --- # 9.3   Обход графа Дерево представляет отношение «один ко многим», тогда как граф обладает большей свободой и может выражать произвольные отношения «многие ко многим». Поэтому дерево можно рассматривать как частный случай графа. Очевидно, что **операции обхода дерева также являются частным случаем операций обхода графа**. И графы, и деревья требуют применения алгоритмов обхода. Способы обхода графа также делятся на два типа: обход в ширину и обход в глубину. ## 9.3.1   Обход в ширину **Обход в ширину - это способ обхода от ближнего к дальнему, при котором начиная с некоторого узла сначала посещают ближайшие вершины, а затем слой за слоем расширяются наружу**. Как показано на рисунке 9-9, начиная с вершины в левом верхнем углу, мы сначала обходим все смежные вершины этой вершины, затем все смежные вершины следующей вершины и так далее, пока не будут посещены все вершины. ![Обход графа в ширину](graph_traversal.assets/graph_bfs.png){ class="animation-figure" }

Рисунок 9-9   Обход графа в ширину

### 1.   Реализация алгоритма BFS обычно реализуется с помощью очереди, код приведен ниже. Очередь обладает свойством «первым пришел - первым вышел», что хорошо соответствует идее BFS «от ближнего к дальнему». 1. Поместить стартовую вершину обхода `startVet` в очередь и запустить цикл. 2. На каждой итерации цикла извлекать вершину из головы очереди и записывать ее посещение, после чего добавлять все смежные вершины этой вершины в хвост очереди. 3. Повторять шаг `2.` до тех пор, пока не будут посещены все вершины. Чтобы предотвратить повторный обход вершин, нам нужно хеш-множество `visited` , в котором записывается, какие вершины уже посещены. !!! tip Хеш-множество можно рассматривать как хеш-таблицу, которая хранит только `key` и не хранит `value` . Оно позволяет выполнять добавление, удаление и проверку наличия `key` за $O(1)$ времени. Благодаря уникальности `key` хеш-множество обычно используется, например, для устранения повторов. === "Python" ```python title="graph_bfs.py" def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: """Обход в ширину""" # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины # Последовательность обхода вершин res = [] # Хеш-множество для хранения уже посещенных вершин visited = set[Vertex]([start_vet]) # Очередь используется для реализации BFS que = deque[Vertex]([start_vet]) # Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while len(que) > 0: vet = que.popleft() # Извлечь головную вершину из очереди res.append(vet) # Отметить посещенную вершину # Обойти все смежные вершины данной вершины for adj_vet in graph.adj_list[vet]: if adj_vet in visited: continue # Пропустить уже посещенную вершину que.append(adj_vet) # Помещать в очередь только непосещенные вершины visited.add(adj_vet) # Отметить эту вершину как посещенную # Вернуть последовательность обхода вершин return res ``` === "C++" ```cpp title="graph_bfs.cpp" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины vector graphBFS(GraphAdjList &graph, Vertex *startVet) { // Последовательность обхода вершин vector res; // Хеш-множество для хранения уже посещенных вершин unordered_set visited = {startVet}; // Очередь используется для реализации BFS queue que; que.push(startVet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (!que.empty()) { Vertex *vet = que.front(); que.pop(); // Извлечь головную вершину из очереди res.push_back(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (auto adjVet : graph.adjList[vet]) { if (visited.count(adjVet)) continue; // Пропустить уже посещенную вершину que.push(adjVet); // Помещать в очередь только непосещенные вершины visited.emplace(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } ``` === "Java" ```java title="graph_bfs.java" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины List graphBFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = new ArrayList<>(); // Хеш-множество для хранения уже посещенных вершин Set visited = new HashSet<>(); visited.add(startVet); // Очередь используется для реализации BFS Queue que = new LinkedList<>(); que.offer(startVet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (!que.isEmpty()) { Vertex vet = que.poll(); // Извлечь головную вершину из очереди res.add(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (Vertex adjVet : graph.adjList.get(vet)) { if (visited.contains(adjVet)) continue; // Пропустить уже посещенную вершину que.offer(adjVet); // Помещать в очередь только непосещенные вершины visited.add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } ``` === "C#" ```csharp title="graph_bfs.cs" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины List GraphBFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин HashSet visited = [startVet]; // Очередь используется для реализации BFS Queue que = new(); que.Enqueue(startVet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.Count > 0) { Vertex vet = que.Dequeue(); // Извлечь головную вершину из очереди res.Add(vet); // Отметить посещенную вершину foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // Пропустить уже посещенную вершину } que.Enqueue(adjVet); // Помещать в очередь только непосещенные вершины visited.Add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } ``` === "Go" ```go title="graph_bfs.go" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { // Последовательность обхода вершин res := make([]Vertex, 0) // Хеш-множество для хранения уже посещенных вершин visited := make(map[Vertex]struct{}) visited[startVet] = struct{}{} // Очередь используется для реализации BFS, срез используется для имитации очереди queue := make([]Vertex, 0) queue = append(queue, startVet) // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины for len(queue) > 0 { // Извлечь головную вершину из очереди vet := queue[0] queue = queue[1:] // Отметить посещенную вершину res = append(res, vet) // Обойти все смежные вершины данной вершины for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // Помещать в очередь только непосещенные вершины if !isExist { queue = append(queue, adjVet) visited[adjVet] = struct{}{} } } } // Вернуть последовательность обхода вершин return res } ``` === "Swift" ```swift title="graph_bfs.swift" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // Последовательность обхода вершин var res: [Vertex] = [] // Хеш-множество для хранения уже посещенных вершин var visited: Set = [startVet] // Очередь используется для реализации BFS var que: [Vertex] = [startVet] // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while !que.isEmpty { let vet = que.removeFirst() // Извлечь головную вершину из очереди res.append(vet) // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // Пропустить уже посещенную вершину } que.append(adjVet) // Помещать в очередь только непосещенные вершины visited.insert(adjVet) // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res } ``` === "JS" ```javascript title="graph_bfs.js" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphBFS(graph, startVet) { // Последовательность обхода вершин const res = []; // Хеш-множество для хранения уже посещенных вершин const visited = new Set(); visited.add(startVet); // Очередь используется для реализации BFS const que = [startVet]; // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.length) { const vet = que.shift(); // Извлечь головную вершину из очереди res.push(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } que.push(adjVet); // Помещать в очередь только непосещенные вершины visited.add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } ``` === "TS" ```typescript title="graph_bfs.ts" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // Последовательность обхода вершин const res: Vertex[] = []; // Хеш-множество для хранения уже посещенных вершин const visited: Set = new Set(); visited.add(startVet); // Очередь используется для реализации BFS const que = [startVet]; // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.length) { const vet = que.shift(); // Извлечь головную вершину из очереди res.push(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet) ?? []) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } que.push(adjVet); // Помещать в очередь только непосещенные вершины visited.add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } ``` === "Dart" ```dart title="graph_bfs.dart" /* Обход в ширину */ List graphBFS(GraphAdjList graph, Vertex startVet) { // Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин Set visited = {}; visited.add(startVet); // Очередь используется для реализации BFS Queue que = Queue(); que.add(startVet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (que.isNotEmpty) { Vertex vet = que.removeFirst(); // Извлечь головную вершину из очереди res.add(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // Пропустить уже посещенную вершину } que.add(adjVet); // Помещать в очередь только непосещенные вершины visited.add(adjVet); // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res; } ``` === "Rust" ```rust title="graph_bfs.rs" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // Последовательность обхода вершин let mut res = vec![]; // Хеш-множество для хранения уже посещенных вершин let mut visited = HashSet::new(); visited.insert(start_vet); // Очередь используется для реализации BFS let mut que = VecDeque::new(); que.push_back(start_vet); // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while let Some(vet) = que.pop_front() { res.push(vet); // Отметить посещенную вершину // Обойти все смежные вершины данной вершины if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // Пропустить уже посещенную вершину } que.push_back(adj_vet); // Помещать в очередь только непосещенные вершины visited.insert(adj_vet); // Отметить эту вершину как посещенную } } } // Вернуть последовательность обхода вершин res } ``` === "C" ```c title="graph_bfs.c" /* Структура очереди узлов */ typedef struct { Vertex *vertices[MAX_SIZE]; int front, rear, size; } Queue; /* Конструктор */ Queue *newQueue() { Queue *q = (Queue *)malloc(sizeof(Queue)); q->front = q->rear = q->size = 0; return q; } /* Проверка, пуста ли очередь */ int isEmpty(Queue *q) { return q->size == 0; } /* Операция добавления в очередь */ void enqueue(Queue *q, Vertex *vet) { q->vertices[q->rear] = vet; q->rear = (q->rear + 1) % MAX_SIZE; q->size++; } /* Операция извлечения из очереди */ Vertex *dequeue(Queue *q) { Vertex *vet = q->vertices[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->size--; return vet; } /* Проверить, была ли вершина уже посещена */ int isVisited(Vertex **visited, int size, Vertex *vet) { // Искать узел обходом за O(n) времени for (int i = 0; i < size; i++) { if (visited[i] == vet) return 1; } return 0; } /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { // Очередь используется для реализации BFS Queue *queue = newQueue(); enqueue(queue, startVet); visited[(*visitedSize)++] = startVet; // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (!isEmpty(queue)) { Vertex *vet = dequeue(queue); // Извлечь головную вершину из очереди res[(*resSize)++] = vet; // Отметить посещенную вершину // Обойти все смежные вершины данной вершины AdjListNode *node = findNode(graph, vet); while (node != NULL) { // Пропустить уже посещенную вершину if (!isVisited(visited, *visitedSize, node->vertex)) { enqueue(queue, node->vertex); // Помещать в очередь только непосещенные вершины visited[(*visitedSize)++] = node->vertex; // Отметить эту вершину как посещенную } node = node->next; } } // Освободить память free(queue); } ``` === "Kotlin" ```kotlin title="graph_bfs.kt" /* Обход в ширину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { // Последовательность обхода вершин val res = mutableListOf() // Хеш-множество для хранения уже посещенных вершин val visited = HashSet() visited.add(startVet) // Очередь используется для реализации BFS val que = LinkedList() que.offer(startVet) // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while (!que.isEmpty()) { val vet = que.poll() // Извлечь головную вершину из очереди res.add(vet) // Отметить посещенную вершину // Обойти все смежные вершины данной вершины for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // Пропустить уже посещенную вершину que.offer(adjVet) // Помещать в очередь только непосещенные вершины visited.add(adjVet) // Отметить эту вершину как посещенную } } // Вернуть последовательность обхода вершин return res } ``` === "Ruby" ```ruby title="graph_bfs.rb" ### Обход в ширину ### def graph_bfs(graph, start_vet) # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины # Последовательность обхода вершин res = [] # Хеш-множество для хранения уже посещенных вершин visited = Set.new([start_vet]) # Очередь используется для реализации BFS que = [start_vet] # Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины while que.length > 0 vet = que.shift # Извлечь головную вершину из очереди res << vet # Отметить посещенную вершину # Обойти все смежные вершины данной вершины for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # Пропустить уже посещенную вершину que << adj_vet # Помещать в очередь только непосещенные вершины visited.add(adj_vet) # Отметить эту вершину как посещенную end end # Вернуть последовательность обхода вершин res end ``` ??? pythontutor "Визуализация кода"
Код сравнительно абстрактен, поэтому для лучшего понимания рекомендуется сопоставлять его с тем, что показано на рисунке 9-10. === "<1>" ![Шаги обхода графа в ширину](graph_traversal.assets/graph_bfs_step1.png){ class="animation-figure" } === "<2>" ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png){ class="animation-figure" } === "<3>" ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png){ class="animation-figure" } === "<4>" ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png){ class="animation-figure" } === "<5>" ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png){ class="animation-figure" } === "<6>" ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png){ class="animation-figure" } === "<7>" ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png){ class="animation-figure" } === "<8>" ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png){ class="animation-figure" } === "<9>" ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png){ class="animation-figure" } === "<10>" ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png){ class="animation-figure" } === "<11>" ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png){ class="animation-figure" }

Рисунок 9-10   Шаги обхода графа в ширину

!!! question "Является ли последовательность обхода в ширину единственной?" Нет. Обход в ширину требует только соблюдения порядка «от ближнего к дальнему», **а порядок обхода нескольких вершин на одинаковом расстоянии может произвольно меняться**. Например, на рисунке 9-10 можно поменять местами порядок посещения вершин $1$ и $3$ , а вершины $2$, $4$, $6$ также можно переставлять произвольно. ### 2.   Анализ сложности **Временная сложность**: все вершины по одному разу помещаются в очередь и извлекаются из нее, что требует $O(|V|)$ времени. При обходе смежных вершин, поскольку граф неориентированный, все ребра будут посещены по $2$ раза, что требует $O(2|E|)$ времени. В сумме получается $O(|V| + |E|)$ . **Пространственная сложность**: список `res` , хеш-множество `visited` и очередь `que` в худшем случае могут содержать до $|V|$ вершин, поэтому требуется $O(|V|)$ памяти. ## 9.3.2   Обход в глубину **Обход в глубину - это способ обхода, при котором сначала идут до самого конца, а когда дальше идти нельзя, возвращаются назад**. Как показано на рисунке 9-11, начиная с вершины в левом верхнем углу, мы выбираем некоторую смежную вершину текущей вершины, идем до упора, затем возвращаемся назад, снова идем до упора и так далее, пока не будут посещены все вершины. ![Обход графа в глубину](graph_traversal.assets/graph_dfs.png){ class="animation-figure" }

Рисунок 9-11   Обход графа в глубину

### 1.   Реализация алгоритма Такой алгоритмический шаблон «дойти до конца и вернуться» обычно реализуется через рекурсию. Подобно обходу в ширину, в обходе в глубину мы также используем хеш-множество `visited` для записи уже посещенных вершин и тем самым избегаем повторного посещения. === "Python" ```python title="graph_dfs.py" def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): """Вспомогательная функция обхода в глубину""" res.append(vet) # Отметить посещенную вершину visited.add(vet) # Отметить эту вершину как посещенную # Обойти все смежные вершины данной вершины for adjVet in graph.adj_list[vet]: if adjVet in visited: continue # Пропустить уже посещенную вершину # Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet) def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: """Обход в глубину""" # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины # Последовательность обхода вершин res = [] # Хеш-множество для хранения уже посещенных вершин visited = set[Vertex]() dfs(graph, visited, res, start_vet) return res ``` === "C++" ```cpp title="graph_dfs.cpp" /* Вспомогательная функция обхода в глубину */ void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { res.push_back(vet); // Отметить посещенную вершину visited.emplace(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (Vertex *adjVet : graph.adjList[vet]) { if (visited.count(adjVet)) continue; // Пропустить уже посещенную вершину // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины vector graphDFS(GraphAdjList &graph, Vertex *startVet) { // Последовательность обхода вершин vector res; // Хеш-множество для хранения уже посещенных вершин unordered_set visited; dfs(graph, visited, res, startVet); return res; } ``` === "Java" ```java title="graph_dfs.java" /* Вспомогательная функция обхода в глубину */ void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { res.add(vet); // Отметить посещенную вершину visited.add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (Vertex adjVet : graph.adjList.get(vet)) { if (visited.contains(adjVet)) continue; // Пропустить уже посещенную вершину // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины List graphDFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = new ArrayList<>(); // Хеш-множество для хранения уже посещенных вершин Set visited = new HashSet<>(); dfs(graph, visited, res, startVet); return res; } ``` === "C#" ```csharp title="graph_dfs.cs" /* Вспомогательная функция обхода в глубину */ void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { res.Add(vet); // Отметить посещенную вершину visited.Add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины foreach (Vertex adjVet in graph.adjList[vet]) { if (visited.Contains(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины DFS(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины List GraphDFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин HashSet visited = []; DFS(graph, visited, res, startVet); return res; } ``` === "Go" ```go title="graph_dfs.go" /* Вспомогательная функция обхода в глубину */ func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { // Операция append возвращает новую ссылку, поэтому исходную ссылку нужно заново присвоить новому срезу *res = append(*res, vet) visited[vet] = struct{}{} // Обойти все смежные вершины данной вершины for _, adjVet := range g.adjList[vet] { _, isExist := visited[adjVet] // Рекурсивно обходить смежные вершины if !isExist { dfs(g, visited, res, adjVet) } } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { // Последовательность обхода вершин res := make([]Vertex, 0) // Хеш-множество для хранения уже посещенных вершин visited := make(map[Vertex]struct{}) dfs(g, visited, &res, startVet) // Вернуть последовательность обхода вершин return res } ``` === "Swift" ```swift title="graph_dfs.swift" /* Вспомогательная функция обхода в глубину */ func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { res.append(vet) // Отметить посещенную вершину visited.insert(vet) // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for adjVet in graph.adjList[vet] ?? [] { if visited.contains(adjVet) { continue // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { // Последовательность обхода вершин var res: [Vertex] = [] // Хеш-множество для хранения уже посещенных вершин var visited: Set = [] dfs(graph: graph, visited: &visited, res: &res, vet: startVet) return res } ``` === "JS" ```javascript title="graph_dfs.js" /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function dfs(graph, visited, res, vet) { res.push(vet); // Отметить посещенную вершину visited.add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphDFS(graph, startVet) { // Последовательность обхода вершин const res = []; // Хеш-множество для хранения уже посещенных вершин const visited = new Set(); dfs(graph, visited, res, startVet); return res; } ``` === "TS" ```typescript title="graph_dfs.ts" /* Вспомогательная функция обхода в глубину */ function dfs( graph: GraphAdjList, visited: Set, res: Vertex[], vet: Vertex ): void { res.push(vet); // Отметить посещенную вершину visited.add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (const adjVet of graph.adjList.get(vet)) { if (visited.has(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { // Последовательность обхода вершин const res: Vertex[] = []; // Хеш-множество для хранения уже посещенных вершин const visited: Set = new Set(); dfs(graph, visited, res, startVet); return res; } ``` === "Dart" ```dart title="graph_dfs.dart" /* Вспомогательная функция обхода в глубину */ void dfs( GraphAdjList graph, Set visited, List res, Vertex vet, ) { res.add(vet); // Отметить посещенную вершину visited.add(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (Vertex adjVet in graph.adjList[vet]!) { if (visited.contains(adjVet)) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet); } } /* Обход в глубину */ List graphDFS(GraphAdjList graph, Vertex startVet) { // Последовательность обхода вершин List res = []; // Хеш-множество для хранения уже посещенных вершин Set visited = {}; dfs(graph, visited, res, startVet); return res; } ``` === "Rust" ```rust title="graph_dfs.rs" /* Вспомогательная функция обхода в глубину */ fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { res.push(vet); // Отметить посещенную вершину visited.insert(vet); // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины if let Some(adj_vets) = graph.adj_list.get(&vet) { for &adj_vet in adj_vets { if visited.contains(&adj_vet) { continue; // Пропустить уже посещенную вершину } // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adj_vet); } } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { // Последовательность обхода вершин let mut res = vec![]; // Хеш-множество для хранения уже посещенных вершин let mut visited = HashSet::new(); dfs(&graph, &mut visited, &mut res, start_vet); res } ``` === "C" ```c title="graph_dfs.c" /* Проверить, была ли вершина уже посещена */ int isVisited(Vertex **res, int size, Vertex *vet) { // Искать узел обходом за O(n) времени for (int i = 0; i < size; i++) { if (res[i] == vet) { return 1; } } return 0; } /* Вспомогательная функция обхода в глубину */ void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { // Отметить посещенную вершину res[(*resSize)++] = vet; // Обойти все смежные вершины данной вершины AdjListNode *node = findNode(graph, vet); while (node != NULL) { // Пропустить уже посещенную вершину if (!isVisited(res, *resSize, node->vertex)) { // Рекурсивно обходить смежные вершины dfs(graph, res, resSize, node->vertex); } node = node->next; } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { dfs(graph, res, resSize, startVet); } ``` === "Kotlin" ```kotlin title="graph_dfs.kt" /* Вспомогательная функция обхода в глубину */ fun dfs( graph: GraphAdjList, visited: MutableSet, res: MutableList, vet: Vertex? ) { res.add(vet) // Отметить посещенную вершину visited.add(vet) // Отметить эту вершину как посещенную // Обойти все смежные вершины данной вершины for (adjVet in graph.adjList[vet]!!) { if (visited.contains(adjVet)) continue // Пропустить уже посещенную вершину // Рекурсивно обходить смежные вершины dfs(graph, visited, res, adjVet) } } /* Обход в глубину */ // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { // Последовательность обхода вершин val res = mutableListOf() // Хеш-множество для хранения уже посещенных вершин val visited = HashSet() dfs(graph, visited, res, startVet) return res } ``` === "Ruby" ```ruby title="graph_dfs.rb" ### Вспомогательная функция обхода в глубину ### def dfs(graph, visited, res, vet) res << vet # Отметить посещенную вершину visited.add(vet) # Отметить эту вершину как посещенную # Обойти все смежные вершины данной вершины for adj_vet in graph.adj_list[vet] next if visited.include?(adj_vet) # Пропустить уже посещенную вершину # Рекурсивно обходить смежные вершины dfs(graph, visited, res, adj_vet) end end ### Обход в глубину ### def graph_dfs(graph, start_vet) # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины # Последовательность обхода вершин res = [] # Хеш-множество для хранения уже посещенных вершин visited = Set.new dfs(graph, visited, res, start_vet) res end ``` ??? pythontutor "Визуализация кода"
Алгоритмический процесс обхода в глубину показан на рисунке 9-12. - **Прямая пунктирная линия обозначает нисходящую рекурсию** , то есть запуск нового рекурсивного метода для посещения новой вершины. - **Изогнутая пунктирная линия обозначает восходящую рекурсию** , то есть данный рекурсивный метод завершился и управление вернулось туда, откуда он был вызван. Чтобы лучше понять алгоритм, рекомендуется сопоставить код с тем, что показано на рисунке 9-12, и мысленно проследить весь процесс DFS, включая моменты запуска и возврата каждого рекурсивного вызова. === "<1>" ![Шаги обхода графа в глубину](graph_traversal.assets/graph_dfs_step1.png){ class="animation-figure" } === "<2>" ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png){ class="animation-figure" } === "<3>" ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png){ class="animation-figure" } === "<4>" ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png){ class="animation-figure" } === "<5>" ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png){ class="animation-figure" } === "<6>" ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png){ class="animation-figure" } === "<7>" ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png){ class="animation-figure" } === "<8>" ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png){ class="animation-figure" } === "<9>" ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png){ class="animation-figure" } === "<10>" ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png){ class="animation-figure" } === "<11>" ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png){ class="animation-figure" }

Рисунок 9-12   Шаги обхода графа в глубину

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