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

View File

@@ -1130,7 +1130,80 @@ comments: true
=== "Ruby"
```ruby title="graph_adjacency_matrix.rb"
[class]{GraphAdjMat}-[func]{}
### 基於鄰接矩陣實現的無向圖類別 ###
class GraphAdjMat
def initialize(vertices, edges)
### 建構子 ###
# 頂點串列,元素代表“頂點值”,索引代表“頂點索引”
@vertices = []
# 鄰接矩陣,行列索引對應“頂點索引”
@adj_mat = []
# 新增頂點
vertices.each { |val| add_vertex(val) }
# 新增邊
# 請注意edges 元素代表頂點索引,即對應 vertices 元素索引
edges.each { |e| add_edge(e[0], e[1]) }
end
### 獲取頂點數量 ###
def size
@vertices.length
end
### 新增頂點 ###
def add_vertex(val)
n = size
# 向頂點串列中新增新頂點的值
@vertices << val
# 在鄰接矩陣中新增一行
new_row = Array.new(n, 0)
@adj_mat << new_row
# 在鄰接矩陣中新增一列
@adj_mat.each { |row| row << 0 }
end
### 刪除頂點 ###
def remove_vertex(index)
raise IndexError if index >= size
# 在頂點串列中移除索引 index 的頂點
@vertices.delete_at(index)
# 在鄰接矩陣中刪除索引 index 的行
@adj_mat.delete_at(index)
# 在鄰接矩陣中刪除索引 index 的列
@adj_mat.each { |row| row.delete_at(index) }
end
### 新增邊 ###
def add_edge(i, j)
# 參數 i, j 對應 vertices 元素索引
# 索引越界與相等處理
if i < 0 || j < 0 || i >= size || j >= size || i == j
raise IndexError
end
# 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i)
@adj_mat[i][j] = 1
@adj_mat[j][i] = 1
end
### 刪除邊 ###
def remove_edge(i, j)
# 參數 i, j 對應 vertices 元素索引
# 索引越界與相等處理
if i < 0 || j < 0 || i >= size || j >= size || i == j
raise IndexError
end
@adj_mat[i][j] = 0
@adj_mat[j][i] = 0
end
### 列印鄰接矩陣 ###
def __print__
puts "頂點串列 = #{@vertices}"
puts '鄰接矩陣 ='
print_matrix(@adj_mat)
end
end
```
=== "Zig"
@@ -2233,7 +2306,73 @@ comments: true
=== "Ruby"
```ruby title="graph_adjacency_list.rb"
[class]{GraphAdjList}-[func]{}
### 基於鄰接表實現的無向圖類別 ###
class GraphAdjList
attr_reader :adj_list
### 建構子 ###
def initialize(edges)
# 鄰接表key頂點value該頂點的所有鄰接頂點
@adj_list = {}
# 新增所有頂點和邊
for edge in edges
add_vertex(edge[0])
add_vertex(edge[1])
add_edge(edge[0], edge[1])
end
end
### 獲取頂點數量 ###
def size
@adj_list.length
end
### 新增邊 ###
def add_edge(vet1, vet2)
raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)
@adj_list[vet1] << vet2
@adj_list[vet2] << vet1
end
### 刪除邊 ###
def remove_edge(vet1, vet2)
raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)
# 刪除邊 vet1 - vet2
@adj_list[vet1].delete(vet2)
@adj_list[vet2].delete(vet1)
end
### 新增頂點 ###
def add_vertex(vet)
return if @adj_list.include?(vet)
# 在鄰接表中新增一個新鏈結串列
@adj_list[vet] = []
end
### 刪除頂點 ###
def remove_vertex(vet)
raise ArgumentError unless @adj_list.include?(vet)
# 在鄰接表中刪除頂點 vet 對應的鏈結串列
@adj_list.delete(vet)
# 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊
for vertex in @adj_list
@adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)
end
end
### 列印鄰接表 ###
def __print__
puts '鄰接表 ='
for vertex in @adj_list
tmp = @adj_list[vertex.first].map { |v| v.val }
puts "#{vertex.first.val}: #{tmp},"
end
end
end
```
=== "Zig"

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|)$ 空間。