Bài 18.5. Bài tập tổng hợp về duyệt đồ thị
Bài 18.5. Bài tập tổng hợp về duyệt đồ thị
Chào mừng trở lại với series bài viết chuyên sâu về Cấu trúc dữ liệu và Giải thuật của FullhouseDev!
Chúng ta đã cùng nhau khám phá sức mạnh của đồ thị và hai trong số những thuật toán cơ bản nhưng cực kỳ mạnh mẽ để "đi qua" các đỉnh và cạnh của nó: Tìm kiếm theo chiều rộng (BFS) và Tìm kiếm theo chiều sâu (DFS). Hiểu rõ lý thuyết là một chuyện, nhưng để thực sự làm chủ chúng, việc áp dụng vào giải quyết các bài toán thực tế là không thể thiếu.
Bài viết hôm nay sẽ là sân chơi để chúng ta cùng nhau "vật lộn" với một số dạng bài tập kinh điển liên quan đến duyệt đồ thị. Thông qua các ví dụ cụ thể và minh họa bằng C++, chúng ta sẽ thấy cách BFS và DFS được sử dụng linh hoạt như thế nào để giải quyết các vấn đề khác nhau, từ tìm đường đi ngắn nhất đến xác định các thành phần liên thông hay phát hiện chu trình.
Hãy cùng "lăn xả" vào các bài tập để mài sắc kỹ năng duyệt đồ thị của bạn nhé!
1. Nhắc lại nhanh: BFS và DFS trong "combat"
Trước khi bắt tay vào code, hãy cùng nhau điểm lại một chút tinh thần cốt lõi của BFS và DFS khi đối mặt với một bài toán trên đồ thị:
- BFS (Breadth-First Search): Duyệt "theo lớp". Từ một đỉnh xuất phát, BFS sẽ thăm tất cả các đỉnh kề với nó, sau đó mới đến các đỉnh kề với các đỉnh vừa thăm (chưa được thăm trước đó), và cứ thế tiếp tục. Nó sử dụng hàng đợi (queue) để quản lý thứ tự thăm các đỉnh. BFS thường được dùng để tìm đường đi ngắn nhất (theo số cạnh) trong đồ thị không có trọng số.
- DFS (Depth-First Search): Duyệt "theo chiều sâu". Từ một đỉnh xuất phát, DFS sẽ đi sâu nhất có thể theo một nhánh trước khi quay lui (backtrack) để thăm các nhánh khác. Nó sử dụng ngăn xếp (stack) (hoặc đệ quy, về bản chất cũng dùng stack của hệ thống) để quản lý thứ tự thăm các đỉnh. DFS rất hữu ích trong việc kiểm tra tính liên thông, phát hiện chu trình, sắp xếp tô-pô (trong đồ thị có hướng không chu trình), v.v.
Với hai công cụ sắc bén này trong tay, chúng ta hoàn toàn có thể giải quyết một loạt các vấn đề trên đồ thị. Giờ là lúc áp dụng chúng vào thực tế!
2. Bài tập 1: Tìm đường đi ngắn nhất trong đồ thị không trọng số (Sử dụng BFS)
Đây là một ứng dụng kinh điển và trực quan nhất của BFS.
Bài toán: Cho một đồ thị vô hướng, không trọng số với $N$ đỉnh và $M$ cạnh. Tìm độ dài đường đi ngắn nhất (theo số cạnh) từ đỉnh $S$ đến đỉnh $T$.
Phân tích: Vì đồ thị không có trọng số, đường đi ngắn nhất theo số cạnh chính là đường đi mà BFS tìm thấy đầu tiên khi nó thăm đến đỉnh $T$.
Giải thuật:
- Sử dụng một hàng đợi
qđể lưu các đỉnh cần thăm. Ban đầu, thêm đỉnh xuất phát $S$ vào hàng đợi. - Sử dụng một mảng
distđể lưu khoảng cách từ $S$ đến mỗi đỉnh. Khởi tạodist[S] = 0, còn các đỉnh khác là vô cực (hoặc -1 để đánh dấu chưa thăm). - Sử dụng một mảng
visited(hoặc dựa vào giá trị củadist) để đánh dấu các đỉnh đã được thêm vào hàng đợi. Ban đầu, chỉ đỉnh $S$ được đánh dấu là đã thăm (hoặcdist[S] = 0). - Trong khi hàng đợi chưa rỗng:
- Lấy một đỉnh
utừ đầu hàng đợi. - Nếu
ulà đỉnh $T$, ta đã tìm thấy đường đi ngắn nhất. Độ dài chính làdist[u]. Kết thúc. - Duyệt qua tất cả các đỉnh
vkề vớiu:- Nếu
vchưa được thăm (ví dụ:dist[v] == -1):- Cập nhật khoảng cách:
dist[v] = dist[u] + 1. - Đánh dấu
vđã thăm. - Thêm
vvào cuối hàng đợi.
- Cập nhật khoảng cách:
- Nếu
- Lấy một đỉnh
- Nếu hàng đợi rỗng mà chưa gặp đỉnh $T$, nghĩa là $T$ không thể đạt được từ $S$.
Ví dụ minh họa (C++):
Giả sử đồ thị có 6 đỉnh (0 đến 5) và các cạnh sau: (0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5). Tìm đường đi ngắn nhất từ đỉnh 0 đến đỉnh 5.
#include <iostream>
#include <vector>
#include <queue>
#include <limits> // For numeric_limits
using namespace std;
const int INF = numeric_limits<int>::max(); // Sử dụng giá trị vô cực cho khoảng cách
int bfs_shortest_path(const vector<vector<int>>& adj, int start_node, int end_node, int num_nodes) {
// Vector lưu khoảng cách từ start_node đến mỗi đỉnh
vector<int> dist(num_nodes, -1); // Khởi tạo -1 để đánh dấu chưa thăm
// Hàng đợi cho BFS
queue<int> q;
// Khởi tạo cho đỉnh bắt đầu
dist[start_node] = 0;
q.push(start_node);
// Bắt đầu BFS
while (!q.empty()) {
int u = q.front();
q.pop();
// Nếu đã đến đỉnh đích
if (u == end_node) {
return dist[u]; // Trả về khoảng cách tìm được
}
// Duyệt qua các đỉnh kề với u
for (int v : adj[u]) {
// Nếu v chưa được thăm (-1)
if (dist[v] == -1) {
dist[v] = dist[u] + 1; // Cập nhật khoảng cách
q.push(v); // Thêm v vào hàng đợi
}
}
}
// Nếu không tìm thấy đường đi đến end_node
return -1;
}
int main() {
int num_nodes = 6;
// Sử dụng adjacency list để biểu diễn đồ thị
vector<vector<int>> adj(num_nodes);
// Thêm các cạnh (đồ thị vô hướng)
adj[0].push_back(1); adj[1].push_back(0);
adj[0].push_back(2); adj[2].push_back(0);
adj[1].push_back(3); adj[3].push_back(1);
adj[2].push_back(3); adj[3].push_back(2);
adj[2].push_back(4); adj[4].push_back(2);
adj[3].push_back(5); adj[5].push_back(3);
adj[4].push_back(5); adj[5].push_back(4);
int start_node = 0;
int end_node = 5;
int shortest_dist = bfs_shortest_path(adj, start_node, end_node, num_nodes);
if (shortest_dist != -1) {
cout << "Do dai duong di ngan nhat tu " << start_node << " den " << end_node << " la: " << shortest_dist << endl;
} else {
cout << "Khong co duong di tu " << start_node << " den " << end_node << endl;
}
// Output mong đợi: Do dai duong di ngan nhat tu 0 den 5 la: 2 (0 -> 2 -> 5 hoac 0 -> 1 -> 3 -> 5)
// Chú ý: BFS tìm khoảng cách 2 thông qua cả đường 0-2-5 và 0-1-3-5. Khoảng cách là 2 vì 0-2-5 có 2 cạnh.
return 0;
}
Giải thích Code:
- Chúng ta sử dụng
vector<vector<int>> adjđể biểu diễn đồ thị bằng danh sách kề.adj[i]chứa danh sách các đỉnh kề với đỉnhi. - Mảng
distđược khởi tạo với giá trị-1cho tất cả các đỉnh, biểu thị rằng chưa có đỉnh nào được thăm và khoảng cách chưa được biết.dist[start_node]được đặt bằng 0. queue<int> qlưu trữ các đỉnh sẽ được thăm tiếp theo.- Vòng lặp
while (!q.empty())là trái tim của BFS. Chúng ta lần lượt lấy đỉnhutừ đầu hàng đợi. - Nếu
ulà đỉnh đíchend_node, chúng ta đã tìm thấy đường đi ngắn nhất và trả vềdist[u]. - Với mỗi đỉnh
vkề vớiu, chúng ta kiểm tra xemdist[v]có bằng-1không (tức làvchưa được thăm). - Nếu
vchưa thăm, ta cập nhậtdist[v] = dist[u] + 1(khoảng cách đếnvlà khoảng cách đếnucộng thêm 1 cạnh) và thêmvvào hàng đợi. Điều này đảm bảo rằng chúng ta thăm các đỉnh theo từng "lớp" khoảng cách tăng dần. - Nếu vòng lặp kết thúc mà không tìm thấy
end_node(hàng đợi rỗng), nghĩa là không có đường đi từstart_nodeđếnend_node, và chúng ta trả về-1.
3. Bài tập 2: Đếm số thành phần liên thông (Sử dụng DFS)
DFS rất tự nhiên trong việc khám phá toàn bộ một "khu vực" liên thông của đồ thị.
Bài toán: Cho một đồ thị vô hướng với $N$ đỉnh và $M$ cạnh. Đếm số lượng các thành phần liên thông trong đồ thị.
Phân tích: Một thành phần liên thông là một tập hợp con lớn nhất các đỉnh sao cho giữa bất kỳ hai đỉnh nào trong tập hợp đó đều có đường đi. Chúng ta có thể dùng DFS (hoặc BFS) để thăm toàn bộ các đỉnh trong một thành phần liên thông bắt đầu từ một đỉnh bất kỳ trong thành phần đó.
Giải thuật:
- Sử dụng một mảng
visitedđể đánh dấu các đỉnh đã được thăm trong quá trình duyệt. Khởi tạo tất cả đỉnh là chưa thăm. - Sử dụng một biến đếm
countkhởi tạo bằng 0. - Duyệt qua tất cả các đỉnh từ 0 đến $N-1$.
- Nếu một đỉnh
ichưa được thăm:- Tăng biến đếm
countlên 1 (chúng ta vừa tìm thấy một thành phần liên thông mới). - Bắt đầu một cuộc duyệt DFS (hoặc BFS) từ đỉnh
i. Trong quá trình duyệt này, đánh dấu tất cả các đỉnh mà DFS/BFS đi qua là đã thăm.
- Tăng biến đếm
- Sau khi duyệt hết tất cả các đỉnh từ 0 đến $N-1$, biến đếm
countsẽ chứa tổng số thành phần liên thông.
Ví dụ minh họa (C++):
Giả sử đồ thị có 7 đỉnh (0 đến 6) và các cạnh sau: (0, 1), (0, 2), (1, 2), (3, 4), (5, 6). Rõ ràng có 3 thành phần liên thông: {0, 1, 2}, {3, 4}, {5, 6}.
#include <iostream>
#include <vector>
#include <stack> // Có thể dùng stack cho DFS, hoặc đơn giản hơn là dùng đệ quy
using namespace std;
// Hàm DFS đệ quy để thăm tất cả các đỉnh trong một thành phần liên thông
void dfs_visit(const vector<vector<int>>& adj, int u, vector<bool>& visited) {
visited[u] = true; // Đánh dấu đỉnh hiện tại đã thăm
// Duyệt qua các đỉnh kề với u
for (int v : adj[u]) {
// Nếu v chưa được thăm, thực hiện DFS trên v
if (!visited[v]) {
dfs_visit(adj, v, visited);
}
}
}
int count_connected_components(const vector<vector<int>>& adj, int num_nodes) {
// Mảng visited để theo dõi các đỉnh đã được thăm
vector<bool> visited(num_nodes, false);
int count = 0; // Biến đếm số thành phần liên thông
// Duyệt qua tất cả các đỉnh
for (int i = 0; i < num_nodes; ++i) {
// Nếu đỉnh i chưa được thăm, nó là khởi đầu của một thành phần liên thông mới
if (!visited[i]) {
count++; // Tăng số lượng thành phần
dfs_visit(adj, i, visited); // Thực hiện DFS để thăm hết các đỉnh trong thành phần này
}
}
return count;
}
int main() {
int num_nodes = 7;
vector<vector<int>> adj(num_nodes);
// Thêm các cạnh cho ví dụ
adj[0].push_back(1); adj[1].push_back(0);
adj[0].push_back(2); adj[2].push_back(0);
adj[1].push_back(2); adj[2].push_back(1);
adj[3].push_back(4); adj[4].push_back(3);
adj[5].push_back(6); adj[6].push_back(5);
int components = count_connected_components(adj, num_nodes);
cout << "So luong thanh phan lien thong trong do thi la: " << components << endl;
// Output mong đợi: So luong thanh phan lien thong trong do thi la: 3
return 0;
}
Giải thích Code:
- Hàm
dfs_visitlà một hàm trợ giúp dùng đệ quy. Nó nhận đồ thị (adj), đỉnh hiện tạiu, và mảngvisited. Nhiệm vụ của nó là đi sâu vào đồ thị từuvà đánh dấu tất cả các đỉnh reachable từu(trong cùng một thành phần) làtruetrong mảngvisited. - Hàm chính
count_connected_componentskhởi tạo mảngvisitedvà biến đếmcount. - Nó lặp qua tất cả các đỉnh
itừ 0 đếnnum_nodes - 1. - Nếu
visited[i]làfalse, điều này có nghĩa là đỉnhichưa được thăm và nó thuộc về một thành phần liên thông mới mà chúng ta chưa khám phá. - Khi đó, chúng ta tăng
countlên 1 và gọidfs_visitbắt đầu từ đỉnhi. Cuộc gọidfs_visitnày sẽ thăm và đánh dấu tất cả các đỉnh trong cùng thành phần vớii. - Sau khi vòng lặp kết thúc, mỗi lần chúng ta bắt đầu một DFS mới (khi gặp
!visited[i]), điều đó tương ứng với việc khám phá một thành phần liên thông riêng biệt. Do đó,countchính là số lượng thành phần liên thông.
4. Bài tập 3: Phát hiện chu trình trong đồ thị có hướng (Sử dụng DFS)
Phát hiện chu trình là một bài toán quan trọng, đặc biệt là trong đồ thị có hướng khi cần thực hiện sắp xếp tô-pô. DFS có thể giúp chúng ta làm điều này một cách hiệu quả.
Bài toán: Cho một đồ thị có hướng với $N$ đỉnh và $M$ cạnh. Xác định xem đồ thị có chứa ít nhất một chu trình hay không.
Phân tích: Trong đồ thị có hướng, chu trình tồn tại nếu trong quá trình duyệt DFS, chúng ta gặp lại một đỉnh mà đỉnh đó đang nằm trong đường đi hiện tại từ đỉnh xuất phát DFS. Để theo dõi điều này, chúng ta cần nhiều hơn một trạng thái "đã thăm". Chúng ta cần ít nhất ba trạng thái: chưa thăm, đang thăm (trong stack đệ quy hiện tại), đã thăm xong (không còn trong stack đệ quy).
Giải thuật:
- Sử dụng một mảng
visited_stateđể lưu trạng thái của mỗi đỉnh. Có thể dùng 3 giá trị:0: Chưa thăm.1: Đang thăm (đang trong stack đệ quy của DFS).2: Đã thăm xong (đã thoát khỏi stack đệ quy). Khởi tạo tất cả đỉnh với trạng thái0.
- Duyệt qua tất cả các đỉnh từ 0 đến $N-1$.
- Nếu một đỉnh
icó trạng thái0(chưa thăm), gọi hàm DFS kiểm tra chu trình bắt đầu từ đỉnhi. - Hàm DFS kiểm tra chu trình
has_cycle_util(u):- Đặt trạng thái của đỉnh
uthành1(đang thăm). - Duyệt qua tất cả các đỉnh
vkề vớiu:- Nếu trạng thái của
vlà1, nghĩa làvđang trong stack đệ quy hiện tại -> tìm thấy chu trình. Trả vềtrue. - Nếu trạng thái của
vlà0, gọi đệ quyhas_cycle_util(v). Nếu cuộc gọi này trả vềtrue, nghĩa là có chu trình ở nhánh con -> trả vềtrue.
- Nếu trạng thái của
- Sau khi duyệt hết tất cả các đỉnh kề và không tìm thấy chu trình, đặt trạng thái của đỉnh
uthành2(đã thăm xong). - Trả về
false(không tìm thấy chu trình bắt đầu từ nhánhu).
- Đặt trạng thái của đỉnh
- Nếu bất kỳ cuộc gọi
has_cycle_utilnào trả vềtrue, hàm chính sẽ trả vềtrue. - Nếu duyệt hết tất cả các đỉnh mà không có cuộc gọi nào trả về
true, nghĩa là không có chu trình trong đồ thị. Trả vềfalse.
Ví dụ minh họa (C++):
Giả sử đồ thị có hướng 5 đỉnh (0 đến 4) và các cạnh: (0, 1), (0, 2), (1, 2), (2, 3), (3, 4), (4, 2). Chu trình tồn tại: 2 -> 3 -> 4 -> 2.
#include <iostream>
#include <vector>
using namespace std;
// Hàm DFS đệ quy để kiểm tra chu trình
// visited_state: 0 = chua tham, 1 = dang tham (trong stack de quy), 2 = da tham xong
bool has_cycle_util(const vector<vector<int>>& adj, int u, vector<int>& visited_state) {
visited_state[u] = 1; // Danh dau dang tham
// Duyet qua cac dinh ke cua u
for (int v : adj[u]) {
if (visited_state[v] == 1) {
// Gap lai mot dinh dang trong stack de quy -> co chu trinh
return true;
}
if (visited_state[v] == 0) {
// Neu dinh v chua tham, tiep tuc DFS
if (has_cycle_util(adj, v, visited_state)) {
return true; // Tim thay chu trinh o nhanh con
}
}
// Neu visited_state[v] == 2 (da tham xong), bo qua vinh vien
}
visited_state[u] = 2; // Danh dau da tham xong khi thoat khoi de quy cua dinh u
return false; // Khong tim thay chu trinh tu nhanh nay
}
bool has_cycle_directed(const vector<vector<int>>& adj, int num_nodes) {
// Mang luu trang thai tham cua cac dinh
vector<int> visited_state(num_nodes, 0); // 0: chua tham, 1: dang tham, 2: da tham xong
// Duyet qua tat ca cac dinh de dam bao xet het cac thanh phan lien thong
for (int i = 0; i < num_nodes; ++i) {
if (visited_state[i] == 0) {
// Neu dinh i chua tham, bat dau DFS tu i
if (has_cycle_util(adj, i, visited_state)) {
return true; // Tim thay chu trinh
}
}
}
return false; // Khong tim thay chu trinh sau khi duyet het do thi
}
int main() {
int num_nodes = 5;
// Su dung adjacency list de bieu dien do thi co huong
vector<vector<int>> adj(num_nodes);
// Them cac canh cho vi du co chu trinh
adj[0].push_back(1);
adj[0].push_back(2);
adj[1].push_back(2);
adj[2].push_back(3);
adj[3].push_back(4);
adj[4].push_back(2); // Canh tao chu trinh: 2 -> 3 -> 4 -> 2
bool cycle_exists = has_cycle_directed(adj, num_nodes);
if (cycle_exists) {
cout << "Do thi co huong co chua chu trinh." << endl;
} else {
cout << "Do thi co huong khong chua chu trinh." << endl;
}
// Output mong doi: Do thi co huong co chua chu trinh.
// Vi du khong co chu trinh (Do thi co huong khong chu trinh - DAG)
vector<vector<int>> adj_no_cycle(5);
adj_no_cycle[0].push_back(1);
adj_no_cycle[0].push_back(2);
adj_no_cycle[1].push_back(3);
adj_no_cycle[2].push_back(3);
adj_no_cycle[3].push_back(4);
bool cycle_exists_no = has_cycle_directed(adj_no_cycle, num_nodes);
if (cycle_exists_no) {
cout << "Do thi co huong co chua chu trinh (vi du 2)." << endl;
} else {
cout << "Do thi co huong khong chua chu trinh (vi du 2)." << endl;
}
// Output mong doi: Do thi co huong khong chua chu trinh (vi du 2).
return 0;
}
Giải thích Code:
- Chúng ta sử dụng mảng
visited_statevới ba trạng thái (0,1,2) như đã mô tả trong giải thuật. Trạng thái1(đang thăm) là chìa khóa để phát hiện chu trình trong đồ thị có hướng. - Hàm
has_cycle_util(u)thực hiện DFS đệ quy. Khi vào hàm với đỉnhu, ta đặtvisited_state[u] = 1. - Trong vòng lặp duyệt các đỉnh kề
vcủau:- Nếu
visited_state[v] == 1, điều này chỉ xảy ra khivlà một tổ tiên củautrong cây DFS hiện tại vàvchưa bị đánh dấu là2(đã thăm xong). Gặp đỉnhvtrong trạng thái1từ đỉnhutạo thành một cạnh ngược (back edge) trong cây DFS, chỉ ra sự tồn tại của chu trình. Ta trả vềtruengay lập tức. - Nếu
visited_state[v] == 0,vchưa thăm. Ta tiếp tục đệ quyhas_cycle_util(v). Nếu cuộc gọi đệ quy này tìm thấy chu trình ở bất kỳ đâu trong nhánh con củav, nó sẽ trả vềtrue, và chúng ta cũng trả vềtrue. - Nếu
visited_state[v] == 2, nghĩa là đỉnhvvà toàn bộ nhánh con của nó đã được thăm xong và không có chu trình. Chúng ta bỏ qua đỉnhv.
- Nếu
- Sau khi duyệt hết tất cả các đỉnh kề của
umà không tìm thấy chu trình, điều đó có nghĩa là nhánh từukhông tạo ra chu trình ngược nào với các đỉnh đang thăm. Ta đặtvisited_state[u] = 2và trả vềfalse. - Hàm chính
has_cycle_directedlặp qua tất cả các đỉnh. Nếu gặp một đỉnh chưa thăm (visited_state[i] == 0), ta bắt đầu một cuộc gọihas_cycle_util(i). Nếu bất kỳ cuộc gọi nào trả vềtrue, đồ thị có chu trình và hàm chính trả vềtrue. Nếu duyệt hết mà không tìm thấy chu trình nào, trả vềfalse.
Bài tập ví dụ:
[Graph].DFS trên đồ thị có hướng.
Cho đồ thị có hướng G = (V, E) được biểu diễn dưới dạng danh sách cạnh. Hãy thực hiện in ra danh sách các đỉnh được duyệt theo thuật toán DFS(s).
Input Format
Dòng đầu tiên là 2 số n và m và s, tương ứng với số lượng đỉnh, cạnh của đồ thị và đỉnh bắt đầu duyệt. Các đỉnh của đồ thị được đánh số từ 1 tới n. m dòng tiếp theo mỗi dòng chứa đỉnh u, v (u != v) tương ứng với một cạnh của đồ thị. (1<=s<=n<=1000; 1<=m<=n*(n-1)/2)
Constraints
.
Output Format
In ra các đỉnh được duyệt theo thuật toán DFS(s). Chú ý trong quá trình mở rộng các đỉnh của thuật toán DFS luôn lựa chọn duyệt các đỉnh có thứ tự nhỏ hơn trước.
Ví dụ:
Dữ liệu vào
5 9 1
1 2
1 3
1 4
2 1
2 4
2 5
3 4
3 5
4 5
Dữ liệu ra
1 2 4 5 3
Đây là hướng dẫn giải bài [Graph].DFS trên đồ thị có hướng bằng C++:
- Đọc Input: Đọc số đỉnh
n, số cạnhm, và đỉnh bắt đầus. - Biểu diễn đồ thị: Sử dụng danh sách kề (adjacency list). Do các đỉnh được đánh số từ 1 đến n, dùng
vector<vector<int>> adjcó kích thướcn+1. Duyệt quamcạnh, với mỗi cạnhu v, thêmvvào danh sách kề củau(adj[u].push_back(v)). - Sắp xếp danh sách kề: Để đảm bảo duyệt các đỉnh có thứ tự nhỏ hơn trước theo yêu cầu, sau khi đọc tất cả các cạnh, duyệt qua
adj[i]cho mỗi đỉnhitừ 1 đếnnvà sắp xếp (sort) danh sách các đỉnh kề này theo thứ tự tăng dần. - Mảng visited: Khởi tạo một mảng boolean
visitedcó kích thướcn+1, tất cả các giá trị ban đầu làfalse. Mảng này dùng để đánh dấu các đỉnh đã được duyệt. - Hàm DFS đệ quy: Xây dựng một hàm
void dfs(int u, vector<vector<int>>& adj, vector<bool>& visited):- Đánh dấu đỉnh
uđã được thăm (visited[u] = true). - In đỉnh
u. - Duyệt qua tất cả các đỉnh kề
vcủautrongadj[u](lúc này đã được sắp xếp). - Nếu đỉnh
vchưa được thăm (!visited[v]), gọi đệ quydfs(v, adj, visited).
- Đánh dấu đỉnh
- Thực hiện DFS: Từ hàm
main, gọi hàmdfs(s, adj, visited)để bắt đầu quá trình duyệt từ đỉnhs.
Comments