You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hello-algo/en/docs/chapter_graph/graph_traversal.md

425 lines
15 KiB

8 months ago
---
comments: true
---
# 9.3   Graph traversal
Trees represent a "one-to-many" relationship, while graphs have a higher degree of freedom and can represent any "many-to-many" relationship. Therefore, we can consider trees as a special case of graphs. Clearly, **tree traversal operations are also a special case of graph traversal operations**.
7 months ago
Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal can be divided into two types: <u>Breadth-First Search (BFS)</u> and <u>Depth-First Search (DFS)</u>.
8 months ago
## 9.3.1 &nbsp; Breadth-first search
7 months ago
**Breadth-first search is a near-to-far traversal method, starting from a certain node, always prioritizing the visit to the nearest vertices and expanding outwards layer by layer**. As shown in Figure 9-9, starting from the top left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited.
8 months ago
![Breadth-first traversal of a graph](graph_traversal.assets/graph_bfs.png){ class="animation-figure" }
<p align="center"> Figure 9-9 &nbsp; Breadth-first traversal of a graph </p>
### 1. &nbsp; Algorithm implementation
BFS is usually implemented with the help of a queue, as shown in the code below. The queue has a "first in, first out" property, which aligns with the BFS idea of traversing "from near to far".
1. Add the starting vertex `startVet` to the queue and start the loop.
2. In each iteration of the loop, pop the vertex at the front of the queue and record it as visited, then add all adjacent vertices of that vertex to the back of the queue.
3. Repeat step `2.` until all vertices have been visited.
7 months ago
To prevent revisiting vertices, we use a hash set `visited` to record which nodes have been visited.
8 months ago
=== "Python"
```python title="graph_bfs.py"
def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
7 months ago
"""Breadth-first traversal"""
# Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
# Vertex traversal sequence
8 months ago
res = []
7 months ago
# Hash set, used to record visited vertices
8 months ago
visited = set[Vertex]([start_vet])
7 months ago
# Queue used to implement BFS
8 months ago
que = deque[Vertex]([start_vet])
7 months ago
# Starting from vertex vet, loop until all vertices are visited
8 months ago
while len(que) > 0:
7 months ago
vet = que.popleft() # Dequeue the vertex at the head of the queue
res.append(vet) # Record visited vertex
# Traverse all adjacent vertices of that vertex
8 months ago
for adj_vet in graph.adj_list[vet]:
if adj_vet in visited:
7 months ago
continue # Skip already visited vertices
que.append(adj_vet) # Only enqueue unvisited vertices
visited.add(adj_vet) # Mark the vertex as visited
# Return the vertex traversal sequence
8 months ago
return res
```
=== "C++"
```cpp title="graph_bfs.cpp"
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "Java"
```java title="graph_bfs.java"
7 months ago
/* Breadth-first traversal */
// Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
8 months ago
List<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {
7 months ago
// Vertex traversal sequence
8 months ago
List<Vertex> res = new ArrayList<>();
7 months ago
// Hash set, used to record visited vertices
8 months ago
Set<Vertex> visited = new HashSet<>();
visited.add(startVet);
7 months ago
// Queue used to implement BFS
8 months ago
Queue<Vertex> que = new LinkedList<>();
que.offer(startVet);
7 months ago
// Starting from vertex vet, loop until all vertices are visited
8 months ago
while (!que.isEmpty()) {
7 months ago
Vertex vet = que.poll(); // Dequeue the vertex at the head of the queue
res.add(vet); // Record visited vertex
// Traverse all adjacent vertices of that vertex
8 months ago
for (Vertex adjVet : graph.adjList.get(vet)) {
if (visited.contains(adjVet))
7 months ago
continue; // Skip already visited vertices
que.offer(adjVet); // Only enqueue unvisited vertices
visited.add(adjVet); // Mark the vertex as visited
8 months ago
}
}
7 months ago
// Return the vertex traversal sequence
8 months ago
return res;
}
```
=== "C#"
```csharp title="graph_bfs.cs"
7 months ago
[class]{graph_bfs}-[func]{GraphBFS}
8 months ago
```
=== "Go"
```go title="graph_bfs.go"
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "Swift"
```swift title="graph_bfs.swift"
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "JS"
```javascript title="graph_bfs.js"
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "TS"
```typescript title="graph_bfs.ts"
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "Dart"
```dart title="graph_bfs.dart"
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "Rust"
```rust title="graph_bfs.rs"
7 months ago
[class]{}-[func]{graph_bfs}
8 months ago
```
=== "C"
```c title="graph_bfs.c"
7 months ago
[class]{Queue}-[func]{}
8 months ago
7 months ago
[class]{}-[func]{isVisited}
8 months ago
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "Kotlin"
```kotlin title="graph_bfs.kt"
7 months ago
[class]{}-[func]{graphBFS}
8 months ago
```
=== "Ruby"
```ruby title="graph_bfs.rb"
7 months ago
[class]{}-[func]{graph_bfs}
8 months ago
```
=== "Zig"
```zig title="graph_bfs.zig"
[class]{}-[func]{graphBFS}
```
7 months ago
The code is relatively abstract, it is suggested to compare with Figure 9-10 to deepen the understanding.
8 months ago
=== "<1>"
![Steps of breadth-first search of a graph](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" }
<p align="center"> Figure 9-10 &nbsp; Steps of breadth-first search of a graph </p>
!!! question "Is the sequence of breadth-first traversal unique?"
7 months ago
Not unique. Breadth-first traversal only requires traversing in a "from near to far" order, **and the traversal order of multiple vertices at the same distance can be arbitrarily shuffled**. For example, in Figure 9-10, the visitation order of vertices $1$ and $3$ can be switched, as can the order of vertices $2$, $4$, and $6$.
8 months ago
### 2. &nbsp; Complexity analysis
**Time complexity**: All vertices will be enqueued and dequeued once, using $O(|V|)$ time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited $2$ times, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time.
7 months ago
**Space complexity**: The maximum number of vertices in list `res`, hash set `visited`, and queue `que` is $|V|$, using $O(|V|)$ space.
8 months ago
## 9.3.2 &nbsp; Depth-first search
7 months ago
**Depth-first search is a traversal method that prioritizes going as far as possible and then backtracks when no further paths are available**. As shown in Figure 9-11, starting from the top left vertex, visit some adjacent vertex of the current vertex until no further path is available, then return and continue until all vertices are traversed.
8 months ago
![Depth-first traversal of a graph](graph_traversal.assets/graph_dfs.png){ class="animation-figure" }
<p align="center"> Figure 9-11 &nbsp; Depth-first traversal of a graph </p>
### 1. &nbsp; Algorithm implementation
7 months ago
This "go as far as possible and then return" algorithm paradigm is usually implemented based on recursion. Similar to breadth-first search, in depth-first search, we also need the help of a hash set `visited` to record the visited vertices to avoid revisiting.
8 months ago
=== "Python"
```python title="graph_dfs.py"
def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):
7 months ago
"""Depth-first traversal helper function"""
res.append(vet) # Record visited vertex
visited.add(vet) # Mark the vertex as visited
# Traverse all adjacent vertices of that vertex
8 months ago
for adjVet in graph.adj_list[vet]:
if adjVet in visited:
7 months ago
continue # Skip already visited vertices
# Recursively visit adjacent vertices
8 months ago
dfs(graph, visited, res, adjVet)
def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
7 months ago
"""Depth-first traversal"""
# Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
# Vertex traversal sequence
8 months ago
res = []
7 months ago
# Hash set, used to record visited vertices
8 months ago
visited = set[Vertex]()
dfs(graph, visited, res, start_vet)
return res
```
=== "C++"
```cpp title="graph_dfs.cpp"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "Java"
```java title="graph_dfs.java"
7 months ago
/* Depth-first traversal helper function */
8 months ago
void dfs(GraphAdjList graph, Set<Vertex> visited, List<Vertex> res, Vertex vet) {
7 months ago
res.add(vet); // Record visited vertex
visited.add(vet); // Mark the vertex as visited
// Traverse all adjacent vertices of that vertex
8 months ago
for (Vertex adjVet : graph.adjList.get(vet)) {
if (visited.contains(adjVet))
7 months ago
continue; // Skip already visited vertices
// Recursively visit adjacent vertices
8 months ago
dfs(graph, visited, res, adjVet);
}
}
7 months ago
/* Depth-first traversal */
// Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex
8 months ago
List<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {
7 months ago
// Vertex traversal sequence
8 months ago
List<Vertex> res = new ArrayList<>();
7 months ago
// Hash set, used to record visited vertices
8 months ago
Set<Vertex> visited = new HashSet<>();
dfs(graph, visited, res, startVet);
return res;
}
```
=== "C#"
```csharp title="graph_dfs.cs"
7 months ago
[class]{graph_dfs}-[func]{DFS}
8 months ago
7 months ago
[class]{graph_dfs}-[func]{GraphDFS}
8 months ago
```
=== "Go"
```go title="graph_dfs.go"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "Swift"
```swift title="graph_dfs.swift"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "JS"
```javascript title="graph_dfs.js"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "TS"
```typescript title="graph_dfs.ts"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "Dart"
```dart title="graph_dfs.dart"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "Rust"
```rust title="graph_dfs.rs"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graph_dfs}
8 months ago
```
=== "C"
```c title="graph_dfs.c"
7 months ago
[class]{}-[func]{isVisited}
8 months ago
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "Kotlin"
```kotlin title="graph_dfs.kt"
7 months ago
[class]{}-[func]{dfs}
8 months ago
7 months ago
[class]{}-[func]{graphDFS}
8 months ago
```
=== "Ruby"
```ruby title="graph_dfs.rb"
7 months ago
[class]{}-[func]{dfs}
[class]{}-[func]{graph_dfs}
8 months ago
```
=== "Zig"
```zig title="graph_dfs.zig"
[class]{}-[func]{dfs}
[class]{}-[func]{graphDFS}
```
7 months ago
The algorithm process of depth-first search is shown in Figure 9-12.
8 months ago
- **Dashed lines represent downward recursion**, indicating that a new recursive method has been initiated to visit a new vertex.
- **Curved dashed lines represent upward backtracking**, indicating that this recursive method has returned to the position where this method was initiated.
7 months ago
To deepen the understanding, it is suggested to combine Figure 9-12 with the code to simulate (or draw) the entire DFS process in your mind, including when each recursive method is initiated and when it returns.
8 months ago
=== "<1>"
![Steps of depth-first search of a graph](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" }
<p align="center"> Figure 9-12 &nbsp; Steps of depth-first search of a graph </p>
!!! question "Is the sequence of depth-first traversal unique?"
Similar to breadth-first traversal, the order of the depth-first traversal sequence is also not unique. Given a certain vertex, exploring in any direction first is possible, that is, the order of adjacent vertices can be arbitrarily shuffled, all being part of depth-first traversal.
Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to preorder, inorder, and postorder traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal.
### 2. &nbsp; Complexity analysis
**Time complexity**: All vertices will be visited once, using $O(|V|)$ time; all edges will be visited twice, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time.
7 months ago
**Space complexity**: The maximum number of vertices in list `res`, hash set `visited` is $|V|$, and the maximum recursion depth is $|V|$, therefore using $O(|V|)$ space.