Tài liệu
Mục tiêu chung của việc Duyệt (đồ thị, mảng, ...)?
Tìm một cách để duyệt qua tất cả phần tử trong cấu trúc, mỗi phần tử một lần.
- Mảng: dùng vòng lặp
forđể xét lần lượt từng phần tử theo thứ tự từ trái sang phải (hoặc ngược lại) - Đồ thị: Có hai kĩ thuật là
- DFS: Depth-first search (Tìm kiếm theo chiều sâu)
- BFS: Breadth-first search (Tìm kiếm theo chiều rộng)
DFS
Bắt đầu từ một đỉnh \(u\), ta lần lượt duyệt các đỉnh bằng cách sau:
- Đánh dấu đỉnh \(u\) hiện tại đã thăm
- Chọn một đỉnh \(v\) kề \(u\) sao cho \(v\) chưa được thăm.
- Nếu tìm được đỉnh \(v\) phù hợp, chuyển sang thăm đỉnh \(v\) và làm tương tự hai điều 1 & 2
- Ngược lại, quay về đỉnh \(u\) cũ ngay trước đó để thực hiện lại bước 2.
Nếu ta đã dựng được danh sách kề graph, ta có thể code như sau:
Python
graph = [[] for i in range(n+1)]
visited = [False] * (n + 1) # đánh dấu tất cả chưa được thăm
def dfs(u):
visited[u] = True
# (thông thường) xử lý đỉnh u
for v in graph[u]: # với mỗi đỉnh kề với u
if visited[v] == True: # đỉnh nào thăm rồi thì bỏ qua
continue
dfs(v)
C++
vector<int> graph[MAX];
bool visited[MAX] = {};
void dfs(int u){
visited[u] = true;
// (thông thường) xử lý đỉnh u ở đây
for (int v: graph[u]){
if (visited[v] == true)
continue;
dfs(v);
}
}
Lưu ý với Python
Để đệ quy được sâu, thêm 2 dòng này vào đầu bài:
Python
import sys
sys.setrecursionlimit(độ sâu đệ quy cần dùng)
BFS
- Tạo ra một danh sách chờ các đỉnh
- Từ một đỉnh, ta duyệt hết toàn bộ các đỉnh chưa được thăm kề nó, rồi cho vào danh chờ
- Tiếp tục rút một đỉnh từ danh sách chờ và làm lại điều 2
C++
bool visited[MAX];
int distance[MAX];
queue<int> q;
void bfs(int root){
visited[root] = true;
distance[root] = 0;
q.push(root);
while (!q.empty()){
int node = q.front(); q.pop();
for (int child: graph[node])
if (visited[child] == true) continue;
else {
// giống dòng đầu
visited[child] = true;
distance[child] = distance[node] + 1;
q.push(child);
}
}
}
Tính chất
- (mỗi một lượt,) DFS/BFS chỉ duyệt được trên một thành phần liên thông.
- Nếu sắp xếp lại danh sách cạnh, DFS sẽ luôn cho được đường đi có thứ tự từ điển nhỏ nhất.
- Chứng minh: giả sử \(u\) kề \(v_1\) và \(v_2\), và \(v_1 < v_2\). Để có thứ tự từ điển nhỏ nhất, \(u\) bắt buộc phải đi tới \(v_1\) trước, thay vì \(v_2\). Vì vậy thuật toán trên phải tạo được đường đi tttđ nhỏ nhất.
- BFS tạo ra danh sách mà khoảng cách của các đỉnh được duyệt tới đỉnh ban đầu tăng dần.
Đọc thêm
Các nguồn lý thuyết (English):
- https://csacademy.com/lessons/
- https://usaco.guide/bronze/intro-graphs?lang=cpp
- https://usaco.guide/silver/graph-traversal?lang=cpp
- https://usaco.guide/gold/unweighted-shortest-paths?lang=cpp
- https://usaco.guide/gold/shortest-paths?lang=cpp
- https://cp-algorithms.com/
Tiếng Việt:
- https://vnoi.info/wiki/algo/graph-theory/breadth-first-search.md
- https://vnoi.info/wiki/algo/graph-theory/Depth-First-Search-Tree.md
- https://vnoi.info/wiki/algo/graph-theory/shortest-path.md
- https://howkteam.vn/course/cau-truc-du-lieu-va-giai-thuat/bfs-va-dfs-4320
- https://viblo.asia/p/cac-giai-thuat-tim-kiem-tren-do-thi-1Je5EBRGKnL
Tìm kiếm thêm bài tập?
- https://lqdoj.edu.vn/contests/?contest=&orgs=75
- https://lqdoj.edu.vn/contest/graphmarathon
- HackerRank, CSAcademy, Kattis, Atcoder, Codeforces, \(\dots\)



Bình luận