This commit is contained in:
krahets
2024-04-28 22:35:59 +08:00
parent f986ae3c8c
commit f748af6aa4
34 changed files with 588 additions and 136 deletions
+74 -29
View File
@@ -24,7 +24,11 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
2. 在迴圈的每輪迭代中,彈出佇列首頂點並記錄訪問,然後將該頂點的所有鄰接頂點加入到佇列尾部。
3. 迴圈步驟 `2.` ,直到所有頂點被訪問完畢後結束。
為了防止重複走訪頂點,我們需要藉助一個雜湊 `visited` 來記錄哪些節點已被訪問。
為了防止重複走訪頂點,我們需要藉助一個雜湊集合 `visited` 來記錄哪些節點已被訪問。
!!! tip
雜湊集合可以看作一個只儲存 `key` 而不儲存 `value` 的雜湊表,它可以在 $O(1)$ 時間複雜度下進行 `key` 的增刪查改操作。根據 `key` 的唯一性,雜湊集合通常用於資料去重等場景。
=== "Python"
@@ -34,7 +38,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
# 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點
# 頂點走訪序列
res = []
# 雜湊,用於記錄已被訪問過的頂點
# 雜湊集合,用於記錄已被訪問過的頂點
visited = set[Vertex]([start_vet])
# 佇列用於實現 BFS
que = deque[Vertex]([start_vet])
@@ -60,7 +64,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
vector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {
// 頂點走訪序列
vector<Vertex *> res;
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
unordered_set<Vertex *> visited = {startVet};
// 佇列用於實現 BFS
queue<Vertex *> que;
@@ -91,7 +95,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
List<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {
// 頂點走訪序列
List<Vertex> res = new ArrayList<>();
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
Set<Vertex> visited = new HashSet<>();
visited.add(startVet);
// 佇列用於實現 BFS
@@ -122,7 +126,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
List<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {
// 頂點走訪序列
List<Vertex> res = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
HashSet<Vertex> visited = [startVet];
// 佇列用於實現 BFS
Queue<Vertex> que = new();
@@ -153,7 +157,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
func graphBFS(g *graphAdjList, startVet Vertex) []Vertex {
// 頂點走訪序列
res := make([]Vertex, 0)
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
visited := make(map[Vertex]struct{})
visited[startVet] = struct{}{}
// 佇列用於實現 BFS, 使用切片模擬佇列
@@ -189,7 +193,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {
// 頂點走訪序列
var res: [Vertex] = []
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
var visited: Set<Vertex> = [startVet]
// 佇列用於實現 BFS
var que: [Vertex] = [startVet]
@@ -219,7 +223,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
function graphBFS(graph, startVet) {
// 頂點走訪序列
const res = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
const visited = new Set();
visited.add(startVet);
// 佇列用於實現 BFS
@@ -250,7 +254,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {
// 頂點走訪序列
const res: Vertex[] = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
const visited: Set<Vertex> = new Set();
visited.add(startVet);
// 佇列用於實現 BFS
@@ -281,7 +285,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點
// 頂點走訪序列
List<Vertex> res = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
Set<Vertex> visited = {};
visited.add(startVet);
// 佇列用於實現 BFS
@@ -313,7 +317,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {
// 頂點走訪序列
let mut res = vec![];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
let mut visited = HashSet::new();
visited.insert(start_vet);
// 佇列用於實現 BFS
@@ -421,7 +425,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {
// 頂點走訪序列
val res = mutableListOf<Vertex?>()
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
val visited = HashSet<Vertex>()
visited.add(startVet)
// 佇列用於實現 BFS
@@ -447,7 +451,29 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
=== "Ruby"
```ruby title="graph_bfs.rb"
[class]{}-[func]{graph_bfs}
### 廣度優先走訪 ###
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
```
=== "Zig"
@@ -506,7 +532,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
**時間複雜度**:所有頂點都會入列並出隊一次,使用 $O(|V|)$ 時間;在走訪鄰接頂點的過程中,由於是無向圖,因此所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。
**空間複雜度**:串列 `res` ,雜湊 `visited` ,佇列 `que` 中的頂點數量最多為 $|V|$ ,使用 $O(|V|)$ 空間。
**空間複雜度**:串列 `res` ,雜湊集合 `visited` ,佇列 `que` 中的頂點數量最多為 $|V|$ ,使用 $O(|V|)$ 空間。
## 9.3.2 &nbsp; 深度優先走訪
@@ -518,7 +544,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
### 1. &nbsp; 演算法實現
這種“走到盡頭再返回”的演算法範式通常基於遞迴來實現。與廣度優先走訪類似,在深度優先走訪中,我們也需要藉助一個雜湊 `visited` 來記錄已被訪問的頂點,以避免重複訪問頂點。
這種“走到盡頭再返回”的演算法範式通常基於遞迴來實現。與廣度優先走訪類似,在深度優先走訪中,我們也需要藉助一個雜湊集合 `visited` 來記錄已被訪問的頂點,以避免重複訪問頂點。
=== "Python"
@@ -539,7 +565,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
# 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點
# 頂點走訪序列
res = []
# 雜湊,用於記錄已被訪問過的頂點
# 雜湊集合,用於記錄已被訪問過的頂點
visited = set[Vertex]()
dfs(graph, visited, res, start_vet)
return res
@@ -566,7 +592,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
vector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {
// 頂點走訪序列
vector<Vertex *> res;
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
unordered_set<Vertex *> visited;
dfs(graph, visited, res, startVet);
return res;
@@ -594,7 +620,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
List<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {
// 頂點走訪序列
List<Vertex> res = new ArrayList<>();
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
Set<Vertex> visited = new HashSet<>();
dfs(graph, visited, res, startVet);
return res;
@@ -623,7 +649,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
List<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {
// 頂點走訪序列
List<Vertex> res = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
HashSet<Vertex> visited = [];
DFS(graph, visited, res, startVet);
return res;
@@ -653,7 +679,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
func graphDFS(g *graphAdjList, startVet Vertex) []Vertex {
// 頂點走訪序列
res := make([]Vertex, 0)
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
visited := make(map[Vertex]struct{})
dfs(g, visited, &res, startVet)
// 返回頂點走訪序列
@@ -683,7 +709,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {
// 頂點走訪序列
var res: [Vertex] = []
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
var visited: Set<Vertex> = []
dfs(graph: graph, visited: &visited, res: &res, vet: startVet)
return res
@@ -713,7 +739,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
function graphDFS(graph, startVet) {
// 頂點走訪序列
const res = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
const visited = new Set();
dfs(graph, visited, res, startVet);
return res;
@@ -747,7 +773,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {
// 頂點走訪序列
const res: Vertex[] = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
const visited: Set<Vertex> = new Set();
dfs(graph, visited, res, startVet);
return res;
@@ -780,7 +806,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
List<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {
// 頂點走訪序列
List<Vertex> res = [];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
Set<Vertex> visited = {};
dfs(graph, visited, res, startVet);
return res;
@@ -811,7 +837,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {
// 頂點走訪序列
let mut res = vec![];
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
let mut visited = HashSet::new();
dfs(&graph, &mut visited, &mut res, start_vet);
@@ -882,7 +908,7 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList<Vertex?> {
// 頂點走訪序列
val res = mutableListOf<Vertex?>()
// 雜湊,用於記錄已被訪問過的頂點
// 雜湊集合,用於記錄已被訪問過的頂點
val visited = HashSet<Vertex?>()
dfs(graph, visited, res, startVet)
return res
@@ -892,9 +918,28 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
=== "Ruby"
```ruby title="graph_dfs.rb"
[class]{}-[func]{dfs}
### 深度優先走訪輔助函式 ###
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
[class]{}-[func]{graph_dfs}
### 深度優先走訪 ###
def graph_dfs(graph, start_vet)
# 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點
# 頂點走訪序列
res = []
# 雜湊集合,用於記錄已被訪問過的頂點
visited = Set.new
dfs(graph, visited, res, start_vet)
res
end
```
=== "Zig"
@@ -962,4 +1007,4 @@ BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入
**時間複雜度**:所有頂點都會被訪問 $1$ 次,使用 $O(|V|)$ 時間;所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。
**空間複雜度**:串列 `res` ,雜湊 `visited` 頂點數量最多為 $|V|$ ,遞迴深度最大為 $|V|$ ,因此使用 $O(|V|)$ 空間。
**空間複雜度**:串列 `res` ,雜湊集合 `visited` 頂點數量最多為 $|V|$ ,遞迴深度最大為 $|V|$ ,因此使用 $O(|V|)$ 空間。