แหล่งอ้างอิง: https://cses.fi/book/book.pdf
เขียนทางคณิตศาสตร์ได้ว่า G = (V, E)
Graph ถูกใช้ใน Algorithm และ Competitive Programming จำนวนมาก
แนวคิดเหล่านี้จะถูกใช้ใน Graph Algorithms ต่อไป
ดังนั้นเราจำเป็นต้องมี Graph Representation
Ubon Srisaket Buriram Surin Buriram Yasothon Surin Srisaket Yasothon Ubon
5 4 1 3 1 2 3 4 2 5
ความหมาย:
คำถามคือ เราจะเก็บข้อมูลนี้ในโปรแกรมอย่างไร?
ทำให้ Algorithm บางตัวทำงานช้าลงมาก
ซึ่งจะเป็นพื้นฐานของ Graph Algorithms ทั้งหมด
เหมาะกับ Graph ที่มี Edge จำนวนมาก
//Vertices = {"Annie","Bennie","Cody","Danny","Eddy"}
Vertices = {1,2,3,4,5}
Edges
1 - 2
1 - 3
2 - 3
4 - 5
เราจะเปลี่ยน Graph นี้ให้เป็น Matrix
1 2 3 4
1 [ 0 1 1 0 ]
2 [ 1 0 0 1 ]
3 [ 1 0 0 1 ]
4 [ 0 1 1 0 ]
ค่า 1 หมายถึง มี Edge เชื่อมกัน
const int MAXN = 100;
int adj[MAXN][MAXN];
int main() {
int n, m;
cin >> n >> m;
for(int i = 0; i < m; i++){
int u, v;
cin >> u >> v;
adj[u][v] = 1;
adj[v][u] = 1;
}
}
ถ้า V ใหญ่มาก Matrix จะใช้ memory มาก
ต่อไปเราจะเรียนรู้วิธีที่นิยมใช้มากกว่าใน Competitive Programming
Vertices = {1,2,3,4}
Edges
1 - 2
1 - 3
2 - 4
3 - 4
เราจะเก็บ Graph นี้ในรูปแบบ Adjacency List
1 : 2 3 2 : 1 4 3 : 1 4 4 : 2 3
แถวแต่ละแถวคือ Vertex และเพื่อนบ้าน
Vertex 1 → [2,3] Vertex 2 → [1,4] Vertex 3 → [1,4] Vertex 4 → [2,3]
แต่ละ Vertex จะเก็บเฉพาะ Edge ที่มีอยู่จริง
const int MAXN = 100; vectoradj[MAXN]; int main(){ int n, m; cin >> n >> m; for(int i = 0; i < m; i++){ int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } }
for(int v : adj[u]){
cout << v << endl;
}
ใช้สำหรับ algorithm เช่น DFS / BFS
ดังนั้น Algorithm ส่วนใหญ่จะใช้ Adjacency List
ปัญหาเหล่านี้สามารถแก้ได้ด้วย Disjoint Set Union
Set 1 : {1,2,3}
Set 2 : {4,5}
Set 3 : {6}
แต่ละ element จะมี representative
ใช้ตรวจสอบว่า element อยู่ใน set เดียวกันหรือไม่
1 | 2 | 3
Root ของ tree คือ representative ของ set
const int MAXN = 100005;
int parent[MAXN];
void make_set(int n){
for(int i=1;i<=n;i++)
parent[i] = i;
}
เริ่มต้นให้ทุก node เป็น root ของตัวเอง
ใช้หา root ของ set
int find(int x){
if(parent[x] == x)
return x;
return find(parent[x]);
}
ใช้หา root ของ set
void unite(int a, int b){
a = find(a);
b = find(b);
if(a != b)
parent[b] = a;
}
ใช้รวมสอง set ให้เป็น set เดียวกัน
1 | 2 | 3 | 4 | 5
การทำ find() อาจใช้เวลาถึง O(N)
เทคนิคเหล่านี้ทำให้ DSU ทำงานได้เร็วมาก
ก่อน Find 1 | 2 | 3 | 4
หลังจาก Find จะทำให้ node ชี้ไปที่ root โดยตรง
int find(int x){
if(parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
}
ทำให้ Tree มีความลึกน้อยลง
int parent[MAXN];
int rank[MAXN];
void unite(int a, int b){
a = find(a);
b = find(b);
if(a != b){
if(rank[a] < rank[b])
swap(a,b);
parent[b] = a;
if(rank[a] == rank[b])
rank[a]++;
}
}
ซึ่งถือว่า เกือบ O(1) ในทางปฏิบัติ
6 3 1 2 2 3 4 5
N = 6 (จำนวน node)
M = 3 (จำนวน union operations)
operations:
union(1,2) union(2,3) union(4,5)
Set 1 : {1,2,3}
Set 2 : {4,5}
Set 3 : {6}
node ที่มี root เดียวกัน จะอยู่ใน set เดียวกัน
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int parent[MAXN];
int rankv[MAXN];
int find_set(int x){
if(parent[x] != x)
parent[x] = find_set(parent[x]);
return parent[x];
}
void unite(int a,int b){
a = find_set(a);
b = find_set(b);
if(a != b){
if(rankv[a] < rankv[b])
swap(a,b);
parent[b] = a;
if(rankv[a] == rankv[b])
rankv[a]++;
}
}
int main(){
int N,M;
cin >> N >> M;
for(int i=1;i<=N;i++){
parent[i] = i;
rankv[i] = 0;
}
for(int i=0;i<M;i++){
int a,b;
cin >> a >> b;
unite(a,b);
}
map<int, vector<int>> groups;
for(int i=1;i<=N;i++){
int r = find_set(i);
groups[r].push_back(i);
}
int id = 1;
for(auto g : groups){
cout << "Set " << id++ << " : ";
for(int x : g.second)
cout << x << " ";
cout << endl;
}
}
1
/ \
2 3
/
4
/
5
โครงสร้างของ DSU สามารถมองเป็น ต้นไม้ (Tree) โดย root คือ representative ของ set
find(5) Traversal Path 5 → 4 → 2 → 1
ถ้า tree ลึกมาก การทำ find() จะใช้เวลามากขึ้น
1
/ / | \
2 3 4 5
หลังจากเรียก find() node ทุกตัวจะชี้ตรงไปที่ root
ทำให้การค้นหาครั้งต่อไปเร็วขึ้นมาก