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