Lý thuyết đồ thị | Cấu trúc dữ liệu và giải thuật | Đại học Bách Khoa Hà Nội

Lý thuyết đồ thị | Cấu trúc dữ liệu và giải thuật | Đại học Bách Khoa hà Nội. Tài liệu được biên soạn giúp các bạn tham khảo, củng cố kiến thức, ôn tập và đạt kết quả cao kết thúc học phần. Mời các bạn đọc đón xem!

Trịnh Xuân Đạt
Lý thuyết đồ thị
Đồ thị là cấu trúc dữ liệu g m 2 ph n sau:
Là t p h u h ạn các đỉnh g i là các nút
Một t p h u h n các c p có d ng (u,v) g i là c nh. C p có th t vì (u,v)
khác (v,u) trong trườ ợp đồng h th có hướng (đồ thị động). C p có d ng
(u,v) ch ra r ng có m ột cạnh t nh u từ đ ới đỉnh v. Các cạnh có thể chứa giá
trị (chi phí).
Sau đây là ví dụ về đồ thị vô hướng 5 đỉnh:
Cách bi u di ễn đồ thị đư c sử d ng nhiều nh ất:
O Ma tr n k
O Danh sách k
Có các đại diện khác cũng như, Ma trận tỷ l và Danh sách t l ệ. Việc lựa chọn biểu
diễn đồ thị là tùy theo tình hu ng c thể. Nó hoàn toàn ph thu c vào lo i ho ng ạt độ
đượ c th c hiện và tính d s d ng.
1. Ma trận kề
Ma tr n k m t m ảng 2D có kích thước V*V trong đó V là số đỉnh c ủa đồ thị
adj[][] ra r ng có m, trong đó adj[i][j] = 1 chỉ đỉt cạnh t nh i t nh j. Ma trới đỉ ận
kề cho đồ th hướng luôn đối xứng. Ma trận k c s dề cũng đượ ụng để biể u di n
đồ th có trọng số. Nếu adj [i] [j] = w, thì có m t c nh t nh j có tr ng ừ đỉnh i đến đỉ
số w.
Trịnh Xuân Đạt
Ma tr n k cho bi ví d ên: ểu đồ ụ tr
Ưu điểm: Trình diễ n dễ thực hi n và dễ theo dõi hơn. Việc loại b m ột cạnh mất O
(1) th i gian. Các truy v ấn như liệ đỉnh 'u' đến đỉu có cạnh từ nh 'v' là hi u qu và có
thể được th c hi n O (1).
Nhược điể
m: T n nhi u kho ng tr ). Ngay c ng hơn O (𝑉
2
khi đth thưa thớt
(ch
ứa ít c n s d ng cùng m t không gian. Thêm m nh là O (ạnh hơn), nó vẫ ột đ 𝑉
2
)
thời gian.
Trịnh Xuân Đạt
Cho số đỉnh số cạnh và các thông số cảu đồ thị hãy biểu diễn nó dưới ma trận kề.
Đầu vào:
Số đỉnh
Số cạch
Các thông số kết nối của các đỉnh
Kết quả:
Ma trận kề biểu diễn đồ thị
Test case:
Đầu vào:
4
5
1 2
2 4
3 1
3 4
4 2
Kết qu :
0 1 0 0
0 0 0 1
1 0 0 1
0 1 0 0
Trịnh Xuân Đạt
Code m u:
#include <bits/stdc++.h>
using namespace std;
//by code Đạt Trịnh
bool A[10 10][ ];
void initialize()
{
i i for(int = ; 0 < ; ++10 i)
j j for(int = ; 0 < ; ++10 j)
A i[ ][j] = false;
}
int main()
{
int x, y, nodes, edges;
initialize();
cin >> nodes;
cin >> edges;
i i for(int = ; 0 < edges; ++i)
{
x cin >> >> y;
A x[ ][y] = true;
}
for(int i i= ;1 <= ;++nodes i){
for(int j j= ;1 <= ;++nodes j)
if( [A i][j]){
cout<< <<1 ' ';
}
else cout<< <<0 ' ';
cout<<'\n';
}
}
Trịnh Xuân Đạt
2. Danh sách k
Một m c s dảng danh sách đượ ụng. Kích thước của mảng b ng s nh. Cho m ng đỉ
m ng []. M t m ng mục [i] đại diện cho danh sách các đỉnh k v nh th i . ới đỉ
Biểu diễn này cũng có thể ụng để được sử d biểu di n m có tr ng s . Tr ng ột đồ thị
số c nh có thủa các cạ ể được biểu di i dễn dướ ạng danh sách các cặp. Sau đây là biểu
diễn danh sách kề của biểu đồ trên.
Code bi u di ễn:
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios sync_with_stdio:: (0);
cin. (tie 0);
cout. (tie 0);
int n m, ;
cin >> >> ; n m
vector<int> adj[n];
for(int i= ;0 i m< ;++i){
int x,y;
>> cin x >> ; y
adj[x].push_back(y);
adj[y].push_back(x);
}
int k;
cin >> ; k
cout<<k<<':'
for(auto i adj k: [ ]){
cout<<" "<<i;
}
}
Trịnh Xuân Đạt
Giải thích:
Đồ th gồm n đim và c nh m
vector<int> adj n[ ]; Khai báo danh sách k .
Nạp dữ liệu của đồ th cho danh sách:
Hiện thông tin các đỉnh k v i đỉnh k nào đó:
Duyệt đồ thị:
Có 2 cách ph thông nh duy ất để ệt đồ th đó là loang theo chiều rộng (bfs) và
loang theo chi u sâu (dfs).
1: BFS - Breadth First Search
Phương pháp tiếp cận: V này có th c gi i quy t b ng cách s d ng ấn đề đượ ế
phương thứ ải đầ ộng đơn c truyền t u tiên theo chiều r giản t m t ngu n nh nh. ất đ
Việc triển khai s d ng danh sách k .
for(int i=0;i<m;++i){
int x,y;
>> cin x >> ; y
adj[x].push_back(y);
adj[y].push_back(x);
}
int k;
cin >> ; k
cout<<k<<':'
for(auto i adj: [k]){
cout<<" "<<i;
}
Trịnh Xuân Đạt
Bài ví dụ: Duy in ra kho ng cách t n các nút còn lệt BFS để ừ nút 1 đế i
#include <bits/stdc++.h>
using namespace std;
vector<int> adj[100005];
int d[10005];
void bfs(){
int u, i, v;
queue<int> qu;
qu.push(1);
] = ; d[1 1
while (qu.size()) {
(); u = qu.front
(); qu.pop
for (int i = [0; < i adj u].size(); ++) { i
int v = adj[u][i];
if (d[v] == ) { 0
] = d[v d[u] + ; 1
qu.push(v);
}
}
}
}
int main()
{
int n m, ;
cin >> >> ; n m
for(int i= ;0 i m< ;++i){
int x,y;
>> cin x >> ; y
adj[x].push_back(y);
adj[y].push_back(x);
}
bfs();
for(int i= ;1 i<=n i i;++ )cout<< <<' ';
cout endl<< ;
for(int i= ;1 i<=n i d;++ )cout<< [i]<<' ';
}
Trịnh Xuân Đạt
Bài th c hành:
Cho đồ th gồm n đỉnh m cạnh. Đếm xem đồ thị có bao nhiêu t ng level khác nhau.
Dữ liệu:
Dòng nh t g m 2 s nguyên n , m s nh s c ố đỉ ạnh
m dòng ti p theo m i dòng g m 2 s nguyên x,y nút x n i nút y. ế ối vớ
K t qu : ế
In ra s nguyên duy nh t là s level c ủa đồ thị.
Ví d :
INPUT
5 4
1 2
2 3
3 4
1 4
OUTPUT
3
Trịnh Xuân Đạt
Đề bài:
Bạn đã được cung cấp một bao gồmCây N nút. Cây là một biểu đồ được kết nối
đầy đủ bao gồm N nút và N−1 cạnh.
Đầu vào:
Dòng đầu tiên bao gồm một số nguyên biểu thị số lượng nút trong N
cây. thông Mỗi cái tiếp theo n−1 chứa các số 2 kết nối đỉnh
Số nguyên x
Kết quả:
Bạn cần in một số nguyên duy nhất biểu thị số lượng nút trên cấp x .
Ràng buộc:
Ghi chú
Đảm bảo rằng ít nhất một nút sẽ nằm trên cấp x
Trịnh Xuân Đạt
Test case:
Hà n - 2020ội
Đầu vào:
20
11 1
1 2
13 3
15 4
17 5
11 6
2 7
1 8
15 9
4 10
15 12
5 13
2 14
17 15
15 16
11 17
15 18
9 19
16 20
2
Trịnh Xuân Đạt
Giải thuật: Áp dụng cơ bản BFS có trong hướ ại BFS cơ bảng dẫn t n.
Chương trình:
#include <bits/stdc++.h>
using namespace std;
// Trịnh Xuân Đạt
int n;
vector vector< <int>> G(100005);
vector<bool false> ves(100005, );
vector<int> lev(1000,-1);
void int bfs( s){
queue<int> Q;
Q.push(s);
ves[ ;s]=true
lev[ ;s]=1
while(!Q.empty()){
v int = Q.front();
Q.pop();
for(auto & : [i G v]){
if(ves[i] == false){
lev[i] = lev[v]+1;
Q.push(i);
ves[i] = true;
}
}
}
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i i= ;1 <n i;++ ){
int x y, ;
x cin >> >> y;
G x[ ]. );push_back(y
G y[ ]. );push_back(x
}
int o d, = ;0
cin >> o;
bfs(1);
for(int i i= ;1 <=n;++ ){i
if(lev[i]==o d) ++;
}
cout<<d;
return 0;
}
Trịnh Xuân Đạt
Đề Bài:
Nhà sư đến thăm vùng đấ ần đảt của Qu o. Có tổng cộng N hòn đảo được đánh số từ
1 đến N . Một s c c k t n i v i nhau b ng nh ng cây c u hai chi ặp đảo đượ ế u
chạy trên mặt nước.
Đạt ghét đi qua những cây cầu này vì chúng đòi hỏi rất nhiều n l ực. Anh ta đang
đứ Đả ng ở o s 1 và muốn đến Đảo số N. Tìm t i thi u s ng c u mà anh ta s ố lượ
phải đi qua, nếu anh ta đi theo con đườ ối ưu.ng t
Đầu vào:
Dòng đầu chứa T . Số test ki m tra .
Dòng đầu tiên của mỗi bộ test chứa hai s nguyên N , M .
Mỗi dòng trong s dòng ch a hai s nguyên X Y , bi u th r ng có mố M và ột
cầu n i gi o X o Y . ữa đả và Đả
Kết quả:
In câu tr l i cho từng trư ng hợp kiểm tra trong m t dòng m ới
Ràng buộc:
Test case:
Trịnh Xuân Đạt
Hà N - 2020ội
Giải thuật: BFS đánh dấu level
Để giải được bài này b n c n ph i học qua BFS cơ bản mà tôi đã trình bày trong tài
liệu g c. T bi ừ đó s ết cách đánh dấu level t ng c ấp độ cho các nút trong đồ thị.
Công việc khá đơn giản chỉ là in ra level của nút N trong đồ th đã đánh dấu ấy.
Đầu vào:
2
3 2
1 2
2 3
4 4
1 2
2 3
3 4
4 2
Kết quả:
2
2
Trịnh Xuân Đạt
Chương trình:
#include <bits/stdc++.h>
using namespace std;
int n m, ;
vector vector< <int>> G;
vector<bool> ves;
vector<int> lev;
void int bfs( s){
queue<int> Q;
Q.push(s);
ves[ ;s]=true
lev[ ;s]=0
while(!Q.empty()){
v int = Q.front();
Q.pop();
for(auto & : [i G v]){
if(ves[i] == false){
lev[i] = lev[v]+1;
Q.push(i);
ves[i] = true;
}
}
}
}
void solve(){
n m cin >> >> ;
G.clear(); G.resize(100005);
ves.clear(); ves.resize(1000005,false);
lev.clear() ; lev.resize(10005);
for(int i i= ;0 <m i;++ ){
int x y, ;
x cin >> >> y;
G x[ ]. );push_back(y
G y[ ]. );push_back(x
}
bfs(1);
cout<< ]<<lev[n '\n';
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while( )t--
solve();
return 0;
}
Trịnh Xuân Đạt
| 1/15

Preview text:

Lý thuyết đồ thị
Đồ thị là cấu trúc dữ liệu gồm 2 phần sau:
Là tập hữu hạn các đỉnh gọi là các nút
Một tập hữu hạn các cặp có dạng (u,v) gọi là cạnh. Cặp có thứ tự vì (u,v)
khác (v,u) trong trường hợp đồ thị có hướng (đồ thị động). Cặp có dạng
(u,v) chỉ ra rằng có một cạnh từ đỉnh u tới đỉnh v. Các cạnh có thể chứa giá trị (chi phí).
Sau đây là ví dụ về đồ thị vô hướng 5 đỉnh:
Cách biểu diễn đồ thị đ ợ
ư c sử dụng nhiều nhất: O Ma trận kề O Danh sách kề
Có các đại diện khác cũng như, Ma trận tỷ lệ và Danh sách tỷ lệ. Việc lựa chọn biểu
diễn đồ thị là tùy theo tình huống cụ thể. Nó hoàn toàn phụ thuộc vào loại hoạt động
được thực hiện và tính dễ sử dụng. 1. Ma trận kề
Ma trận kề là một mảng 2D có kích thước V*V trong đó V là số đỉnh của đồ thị
adj[][] , trong đó adj[i][j] = 1 chỉ ra rằng có một cạnh từ đỉnh i tới đỉnh j. Ma trận
kề cho đồ thị vô hướng luôn đối xứng. Ma trận kề cũng được sử dụng để biểu diễn đồ t ị
h có trọng số. Nếu adj [i] [j] = w, thì có một cạnh từ đỉnh i đến đỉnh j có trọng số w. Trịnh Xuân Đạt
Ma trận kề cho biểu đồ ví dụ t ê r n:
Ưu điểm: Trình diễn dễ thực hiện và dễ theo dõi hơn. Việc loại bỏ một cạnh mất O
(1) thời gian. Các truy vấn như liệu có cạnh từ đỉnh 'u' đến đỉnh 'v' là hiệu quả và có
thể được thực hiện O (1).
Nhược điểm: Tốn nhiều khoảng trống hơn O (𝑉2). Ngay cả khi đồ thị thưa thớt
(chứa ít cạnh hơn), nó vẫn sử dụng cùng một không gian. Thêm một đỉnh là O (𝑉2) thời gian. Trịnh Xuân Đạt
Cho số đỉnh số cạnh và các thông số cảu đồ thị hãy biểu diễn nó dưới ma trận kề. Đầu vào: Số đỉnh Số cạch
Các thông số kết nối của các đỉnh Kết quả:
Ma trận kề biểu diễn đồ thị Test case: Đầu vào: Kết quả: 4 0 1 0 0 5 0 0 0 1 1 2 1 0 0 1 2 4 0 1 0 0 3 1 3 4 4 2 Trịnh Xuân Đạt Code mẫu: #include using namespace std; //by code Đạt Trịnh bool A[10] 1 [ 0]; void initialize() { for(int i = 0; i < 1 ; 0 ++i) for(int j = 0; j < 1 ; 0 ++j) A[i][j] = false; } int main() { int x, y, nodes, edges; initialize(); cin >> nodes; cin >> edges;
for(int i = 0; i < edges; ++i) { cin >> x >> y; A[x][y] = true; } for(int i=1;i<=node ; s ++i){ for(int j=1;j<=node ; s ++j) if(A[i][j]){ cout<<1<<' '; }
else cout<<0<<' '; cout<<'\n'; } } Trịnh Xuân Đạt 2. Danh sách kề
Một mảng danh sách được sử dụng. Kích thước của mảng bằng số đỉnh. Cho mảng
là mảng []. Một mảng mục [i] đại diện cho danh sách các đỉnh kề với đỉnh thứ i .
Biểu diễn này cũng có thể được sử dụng để biểu diễn một đồ thị có trọng số. Trọng
số của các cạnh có thể được biểu diễn dưới dạng danh sách các cặp. Sau đây là biểu
diễn danh sách kề của biểu đồ trên. Code biểu diễn: #include using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int n,m; cin >> n >> m; vector adj[n]; for(int i=0;i int x,y; cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); } int k; cin >> k; cout< for(auto i:adj[k]){ cout<<" "< } } Trịnh Xuân Đạt Giải thích: Đồ t ị
h gồm n điểm và m cạnh
vector adj[n]; Khai báo danh sách kề.
Nạp dữ liệu của đồ thị cho danh sách: for(int i=0;i int x,y; cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); }
Hiện thông tin các đỉnh kề với đỉnh k nào đó: int k; cin >> k; cout< for(auto i:adj[k]){ cout<<" "< } Duyệt đồ thị:
Có 2 cách phổ thông nhất để duyệt đồ thị đó là loang theo chiều rộng (bfs) và
loang theo chiều sâu (dfs). 1: BFS - Breadth First Search
Phương pháp tiếp cận: Vấn đề này có thể được giải quyết bằng cách sử dụng
phương thức truyền tải đầu tiên theo chiều rộng đơn giản từ một nguồn nhất định.
Việc triển khai sử dụng danh sách kề. Trịnh Xuân Đạt
Bài ví dụ: Duyệt BFS để in ra khoảng cách từ nút 1 đến các nút còn lại #include using namespace std; vector adj[100005]; int d[10005]; void bfs(){ int u, i, v; queue qu; qu.push(1); d[1] = 1; while (qu.size()) { u = qu.front(); qu.pop(); for (int i = 0; i < adj[u].size(); i++) { int v = adj[u][i]; if (d[v] == 0) { d[v] = d[u] + 1; qu.push(v); } } } } int main() { int n,m; cin >> n >> m; for(int i=0;i int x,y; cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); } bfs();
for(int i=1;i<=n;++i)cout< i < <<' ';
cout< for(int i=1;i<=n;++i)cout< d < [i]<<' '; } Trịnh Xuân Đạt Bài thực hành:
Cho đồ thị gồm n đỉnh m cạnh. Đếm xem đồ thị có bao nhiêu tầng level khác nhau. Dữ liệu:
Dòng nhất gồm 2 số nguyên n , m số đỉnh – số cạnh
m dòng tiếp theo mỗi dòng gồm 2 số nguyên x,y nút x nối với nút y. Kết quả:
In ra số nguyên duy nhất là số level của đồ thị. Ví dụ: INPUT OUTPUT 5 4 3 1 2 2 3 3 4 1 4 Trịnh Xuân Đạt Đề bài:
Bạn đã được cung cấp một Cây bao gồm N nút. Cây là một biểu đồ được kết nối
đầy đủ bao gồm N nút và N−1 cạnh. Đầu vào:
Dòng đầu tiên bao gồm một số nguyên N biểu thị số lượng nút trong
cây. Mỗi cái tiếp theo n−1 chứa các thông số kết nối 2 đỉnh Số nguyên x Kết quả:
Bạn cần in một số nguyên duy nhất biểu thị số lượng nút trên cấp x . Ràng buộc: Ghi chú
Đảm bảo rằng ít nhất một nút sẽ nằm trên cấp x Trịnh Xuân Đạt Test case: Đầu vào: Kết quả: 20 3 11 1 1 2 13 3 15 4 17 5 11 6 2 7 1 8 15 9 4 10 15 12 5 13 2 14 17 15 15 16 11 17 15 18 9 19 16 20 2 Hà nội - 2020 Trịnh Xuân Đạt
Giải thuật: Áp dụng cơ bản BFS có trong hướng dẫn tại BFS cơ bản. Chương trình: #include using namespace std; // Trịnh Xuân Đạt int n; vector> G(100005); vector ves(100005,false); vector lev(1000,-1); void bfs(int s){ queue Q; Q.push(s); ves[s]=tru ; e lev[s]=1; while(!Q.empty()){ int v = Q.front(); Q.pop(); for(auto &i:G[v]){ if(ves[i] == false){ lev[i] = lev[v]+1; Q.push(i); ves[i] = true; } } } } int main() {
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n; for(int i=1;i+ ){ int x,y; cin >> x >> y; G[x].push_back(y); G[y].push_back(x); } int o,d=0; cin >> o; bfs(1); for(int i=1;i<=n;++i){ if(lev[i]==o)d++; } cout< return 0; } Trịnh Xuân Đạt Đề Bài:
Nhà sư đến thăm vùng đất của Quần đảo. Có tổng cộng N hòn đảo được đánh số từ
1 đến N . Một số cặp đảo được kết nối với nhau bằng những cây cầu hai chiều chạy trên mặt nước.
Đạt ghét đi qua những cây cầu này vì chúng đòi hỏi rất nhiều nỗ lực. Anh ta đang
đứng ở Đảo số 1 và muốn đến Đảo số N. Tìm tối thiểu số lượng cầu mà anh ta sẽ
phải đi qua, nếu anh ta đi theo con đường tối ưu. Đầu vào:
Dòng đầu chứa T . Số test kiểm tra .
Dòng đầu tiên của mỗi bộ test chứa hai số nguyên N , M .
Mỗi dòng trong số M dòng chứa hai số nguyên X và Y , biểu thị rằng có một
cầu nối giữa đảo X và Đảo Y . Kết quả:
In câu trả lời cho từng tr ờ
ư ng hợp kiểm tra trong một dòng mới Ràng buộc: Test case: Trịnh Xuân Đạt Đầu vào: Kết quả: 2 2 3 2 2 1 2 2 3 4 4 1 2 2 3 3 4 4 2 Hà Nội - 2020
Giải thuật: BFS đánh dấu level
Để giải được bài này bạn cần phải học qua BFS cơ bản mà tôi đã trình bày trong tài
liệu gốc. Từ đó sẽ biết cách đánh dấu level từng cấp độ cho các nút trong đồ thị.
Công việc khá đơn giản chỉ là in ra level của nút N trong đồ thị đã đánh dấu ấy. Trịnh Xuân Đạt Chương trình: #include using namespace std; int n,m; vector> G; vector ves; vector lev; void bfs(int s){ queue Q; Q.push(s); ves[s]=tru ; e lev[s]=0; while(!Q.empty()){ int v = Q.front(); Q.pop(); for(auto &i:G[v]){ if(ves[i] == false){ lev[i] = lev[v]+1; Q.push(i); ves[i] = true; } } } } void solve(){ cin >> n >> m ; G.clear(); G.resize(100005);
ves.clear(); ves.resize(1000005,false);
lev.clear() ; lev.resize(10005); for(int i=0;i+ ){ int x,y; cin >> x >> y; G[x].push_back(y); G[y].push_back(x); } bfs(1); cout<} int main() {
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t- ) - solve(); return 0; } Trịnh Xuân Đạt Trịnh Xuân Đạt