CODE HOÀN THIỆNconstint MAX = 1000;vector adj[MAX];bool visited[MAX];//Thêm một cạnh vào đồ thị.void addEdgeintu, intv {//Xóa một cạnh khỏi đồ thị.void rmvEdgeintu, intv {adj[u].eraserem
Trang 1BỘ GIÁO DỤC VÀ ĐẠO TẠO TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI TPHCM
KHOA CÔNG NGHỆ THÔNG TIN
BÁO CÁO
CÁC THUẬT TOÁN RỜI RẠC ĐÃ HỌC Ở CHƯƠNG 6-8
Giáo viên hướng dẫn: Trần Thế Vinh Sinh viên thực hiện: Trần Đức Long MSSV: 034205013204
LỚP: CN2301B
Trang 2MỤC LỤC
I EULER
1.Khái niệm
2.Giải code
3.Giải bảng và sơ đồ
II HALMINTON
1.Khái niệm
2.Giải code
3.Giải bảng và sơ đồ
III PRIM
1.Khái niệm
2.Giải code
3.Giải bảng và sơ đồ
IV KRUSKAL
1.Khái niệm
2.Giải code
3.Giải bảng và sơ đồ V DJIKSTRA
1.Khái niệm
2.Giải code
3.Giải bảng và sơ đồ
VI FORD-BELLMAN
1.Khái niệm
2.Giải code
3.Giải bảng và sơ đồ
Trang 3ĐÔI LỜI ĐẾN THẦY
Kính gửi thầy,
Nhân dịp này, con xin gửi đến thầy những lời tri ân chân thành về
sự hướng dẫn và giảng dạy của thầy trong suốt thời gian qua Thật lòng con rất biết ơn vì những kiến thức quý báu mà thầy đã truyền đạt và những hướng dẫn chân thành mà thầy luôn dành cho con.
Thời gian qua, nhờ sự chỉ bảo tận tình của thầy, con đã có cơ hội học hỏi và phát triển không chỉ về mặt kiến thức mà còn về mặt
kỹ năng và tư duy Những bài học thực tế và những phản hồi chi tiết từ thầy đã giúp con tự tin hơn trong việc nghiên cứu và viết tiểu luận.
Con sẽ luôn ghi nhớ những lời khuyên và chỉ dẫn của thầy, và cố gắng áp dụng vào từng bước tiến của mình Xin chân thành cảm
ơn thầy vì tất cả những điều tuyệt vời đã dành cho con trong suốt thời gian qua.
Kính chúc thầy luôn mạnh khỏe và thành công trong công việc giảng dạy, tiếp tục truyền đạt niềm đam mê và kiến thức cho thế
hệ học trò sau này.
Trân trọng,
Trần Đức Long
Trang 4EULER – KHÁI NIỆM
- Đồ thị Euler là đồ thị có chu trình Euler.
- Đồ thị nửa Euler là đồ thị có đường đi Euler.
Thuật toán tìm chu trình Euler
-Đầu vào: Đồ thị Euler G = (V, E) biểu diễn bởi mảng các danh sách kề DK
- Đầu ra: Chu trình vô hưóng Euler với danh sách các đỉnh nằm trong stack EC.
Trang 5Thêm cạch vào đồ thị nếu thiếu cạnh chẵn
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
Xóa một cạnh khỏi đồ thị nếu dư cạnh lẻ
void rmvEdge(int u, int v) {
adj[u].erase(remove(adj[u].begin(), adj[u].end(), v), adj[u].end()); adj[v].erase(remove(adj[v].begin(), adj[v].end(), u), adj[v].end()); }
Trang 6Check cạnh tiếp theo có hợp lệ hay không
bool isValidNextEdge(int u, int v) {
if (adj[u].size() == 1)
return true;
bool visited[MAX];
memset(visited, false, sizeof(visited));
int count1 = DFSCount(u, visited);
rmvEdge(u, v);
memset(visited, false, sizeof(visited));
int count2 = DFSCount(u, visited);
Trang 7Tìm một số đỉnh bắt đầu có số lẻ ( trường hợp cấp bách)
Trang 8CODE HOÀN THIỆN
const int MAX = 1000;
vector < int > adj[MAX];
void rmvEdge( int u , int v ) {
adj[ u ].erase(remove(adj[ u ].begin(), adj[ u ].end(), v ), adj[ u ].end()); adj[ v ].erase(remove(adj[ v ].begin(), adj[ v ].end(), u ), adj[ v ].end()); }
//Thực hiện duyệt DFS để đếm số đỉnh có thể truy cập được từ một đỉnh int DFSCount( int v , bool visited []) {
Trang 9bool isValidNextEdge( int u , int v ) {
if (adj[ u ].size() == 1)
return true ;
bool visited[MAX];
memset(visited, false , sizeof (visited));
int count1 = DFSCount( u , visited);
rmvEdge( u , v );
memset(visited, false , sizeof (visited));
int count2 = DFSCount( u , visited);
addEdge( u , v );
return (count1 <= count2);
}
//In đường đi Euler từ một đỉnh cụ thể.
void printEulerUtil( int u ) {
for ( int v : adj[ u ]) {
if (isValidNextEdge( u , v)) {
cout << u << "-" << v << " " ; rmvEdge( u , v);
printEulerUtil(v);
} }
Trang 11 Kiểm tra tính hợp lệ của cạnh tiếp theo trong đường đi Euler:
cạnh từ u đến v có phải là cạnh hợp lệ để tiếp tục trong đường
đi Euler hay không Cạnh là hợp lệ nếu:
o Nó là cạnh duy nhất từ đỉnh u, hoặc
o Loại bỏ cạnh đó không làm giảm số đỉnh có thể truy cập
từ u.
In đường đi Euler:
Hàm printEulerUtil(int u) in đường đi Euler bắt đầu
từ đỉnh u.
Hàm printEulerTour(int V) tìm và bắt đầu in đường đi
Euler từ đỉnh có số bậc lẻ đầu tiên (nếu có), hoặc từ đỉnh bất kỳ nếu tất cả các đỉnh đều có số bậc chẵn.
Trang 12GIẢI TAY THEO LÝ THUYẾT
Cho đồ thị liên thông như hình ảnh như ta đếm được đỉnh
Suy ra đường đi của euler là 1-0-2-3-4-3-0 như kết quả
code chạy từng đỉnh và đường
HAMILTON-KHÁI NIỆM
Trang 13Định nghĩa
- Đường Hamilton là đường đi qua mỗi đỉnh của đồ thị đúng một lần.
- Chu trình Hamilton là chu trình đi qua mỗi đỉnh của đồ thị đúng một lần
- Đồ thị Hamilton là đồ thị có chu trình Hamilton.
- Đồ thị nửa Hamilton là đồ thị có đường đi Hamilton.
Định lý Dirac
Nếu a V, deg(a) (n/2) thì đồ thị vô hướng G(V,E) có chu trình Hamilton.
Nhận xét
1 Đồ thị có đỉnh bậc ≤ 1 thì không có chu trình Hamilton Nếu đồ thị
có các đỉnh đều có bậc 2 và có một đỉnh bậc 2 thì mọi chu trình Hamilton (nếu có) phải đi qua 2 cạnh kề của đỉnh này 3 Nếu trong
đồ thị có một đỉnh kề với 3 đỉnh bậc 2 thì không có chu trình
Hamilton 2
Trang 14HAMILTON-CODE Thư viện
void printPath(vector<int>& path) {
for (int i = 0; i < path.size(); ++i) {
Check đỉnh kề với path ko
if (find(path.begin(), path.end(), v) != path.end()) return false;
Trang 15if (find(adj[path[pos - 1]].begin(), adj[path[pos - 1]].end(), v) == adj[path[pos - 1]].end())
Trang 16int V; // Số lượng đỉnh của đồ thị
void addEdge(int u, int v) {
Trang 17for (int i = 0; i < path.size(); ++i) {
bool isSafe(int v, vector<int>& path, int pos) {
// Kiểm tra xem đỉnh v có kề với đỉnh cuối cùng trong path không
if (find(path.begin(), path.end(), v) != path.end())
return false;
// Kiểm tra xem có cạnh giữa đỉnh cuối cùng của path và đỉnh v không
if (find(adj[path[pos - 1]].begin(), adj[path[pos - 1]].end(), v) == adj[path[pos
- 1]].end())
return false;
return true;
}
bool hamiltonianCycleUtil(vector<int>& path, int pos) {
// Nếu tất cả các đỉnh đã được thăm
if (pos == V) {
// Kiểm tra xem có cạnh từ đỉnh cuối cùng của path về đỉnh đầu tiên không
if (find(adj[path[pos - 1]].begin(), adj[path[pos - 1]].end(), path[0]) !=
adj[path[pos - 1]].end()) {
Trang 20Kết quả
TÓM TẮT CODE
Thư viện và khai báo:
Đoạn mã bao gồm các thư viện cần thiết: iostream để sử dụng các hàm
nhập/xuất và vector, algorithm để làm việc với các cấu trúc dữ liệu.
Định nghĩa các hằng số và biến toàn cục như số lượng đỉnh tối đa và danh sách
kề để lưu trữ các cạnh của đồ thị.
Hàm addEdge:
Hàm này thêm một cạnh giữa hai đỉnh u và v trong đồ thị Nó sử dụng danh sách
kề adj để lưu trữ các cạnh này.
Hàm printPath:
Hàm này in ra đường đi Hamilton nếu tìm thấy Nó duyệt qua danh sách path và
in từng đỉnh trong đường đi.
Hàm isSafe:
Hàm này kiểm tra xem đỉnh v có thể được thêm vào đường đi hiện tại path tại vị trí pos hay không.
Nó kiểm tra hai điều kiện:
1 Đỉnh v không được xuất hiện trong path trước đó.
2 Đỉnh v phải kề với đỉnh cuối cùng trong path.
Trang 21 Nếu chưa thăm hết các đỉnh, nó thử thêm từng đỉnh vào path và gọi đệ quy để tiếp tục tìm kiếm Nếu không tìm thấy đường đi, nó quay lui và thử đỉnh tiếp theo.
Hàm hamiltonianCycle:
Hàm này khởi tạo danh sách path với kích thước bằng số đỉnh V và giá trị ban đầu là -1.
Đặt đỉnh đầu tiên trong path là 0 (đỉnh bắt đầu của đường đi Hamilton).
Gọi hàm hamiltonianCycleUtil để tìm chu trình Hamilton Nếu không tìm thấy, in ra thông báo không tồn tại đường đi Hamilton.
Hàm main:
Hàm main khởi tạo số đỉnh của đồ thị và thêm các cạnh vào đồ thị bằng cách gọi hàm addEdge.
Cuối cùng, gọi hàm hamiltonianCycle để tìm và in chu trình Hamilton nếu có.
GIẢI CODE -TAY-SƠ ĐỒ TƯ DUY Giai theo lý thuyết
Khởi tạo đồ thị:
Đồ thị với các cạnh đã cho.
Khởi tạo danh sách:
Danh sách đánh dấu các đỉnh đã thăm.
Danh sách lưu chu trình Hamilton.
Trang 23o Thêm cạnh này vào cây khung nhỏ nhất.
o Thêm đỉnh mới vào tập hợp các đỉnh đã được thêm vào cây khung.
o Cập nhật danh sách các cạnh có thể được thêm vào cây khung.
Đầu vào: Đồ thị liên thông có trọng số.
Đầu ra: Cây khung nhỏ nhất của đồ thị.
Độ phức tạp: O(E log V) với E là số cạnh và V là số đỉnh khi sử
Trang 24bool operator<( const Edge & other ) const {
vector < int > key(V, INT_MAX ); // Đặt tất cả các key là vô cùng
vector < bool > inMST(V, false ); // Đánh dấu các đỉnh đã được thêm vào MST
vector < int > parent(V, -1); // Để lưu cây khung nhỏ nhất
pq.push(make_pair(0, src)); // Thêm đỉnh bắt đầu vào hàng đợi ưu tiên
key[src] = 0; // Key của đỉnh bắt đầu là 0
while (!pq.empty()) {
int u = pq.top().second; // Lấy đỉnh có key nhỏ nhất
pq.pop();
if (inMST[u]) continue ;
inMST[u] = true ; // Đánh dấu đỉnh này đã được thêm vào MST
// Duyệt qua các đỉnh kề của u
for ( const auto & edge : adj[u]) {
int v = edge.first;
int weight = edge.second;
CODE HOÀN THIỆN
Trang 25typedef pair < int , int > Edge ;
// Định nghĩa một đồ thị sử dụng danh sách kề (adjacency list)
vector < int > key(V, INT_MAX ); // Đặt tất cả các key là vô cùng
vector < bool > inMST(V, false ); // Đánh dấu các đỉnh đã được thêm vào MST
vector < int > parent(V, -1); // Để lưu cây khung nhỏ nhất
pq.push(make_pair(0, src)); // Thêm đỉnh bắt đầu vào hàng đợi ưu tiên
key[src] = 0; // Key của đỉnh bắt đầu là 0
while (!pq.empty()) {
int u = pq.top().second; // Lấy đỉnh có key nhỏ nhất
pq.pop();
if (inMST[u]) continue ;
inMST[u] = true ; // Đánh dấu đỉnh này đã được thêm vào MST
// Duyệt qua các đỉnh kề của u
for ( const auto & edge : adj[u]) {
int v = edge.first;
int weight = edge.second;
// Nếu đỉnh v chưa nằm trong MST và trọng số của cạnh u-v nhỏ hơn key[v]
if (!inMST[v] && key[v] > weight) {
Trang 26cout << "nhap tung so" << endl;
for ( int i = 0; i < E; ++i) {
int u, v, w;
cin >> u >> v >> w;
graph.addEdge(u, v, w);
}
int result = graph.primMST();
cout << "Min (MST): " << result << endl; return 0;
}
KẾT QUẢ
Trang 27Tóm Tắt
Khởi tạo lớp Graph:
Lớp này có hai thành phần chính là số đỉnh (V) và danh sách kề (adj) để lưu trữ các cạnh và trọng số.
Có phương thức addEdge để thêm cạnh vào đồ thị.
Thuật toán Prim (primMST):
Sử dụng hàng đợi ưu tiên (priority_queue) để lựa chọn cạnh có trọng số nhỏ nhất để thêm vào cây khung.
Bắt đầu từ một đỉnh ngẫu nhiên và cập nhật các trọng số (key) và đỉnh cha (parent) khi mở rộng cây khung.
Duyệt qua các đỉnh kề của đỉnh hiện tại, cập nhật key và parent nếu tìm thấy cạnh có trọng số nhỏ hơn.
Đầu vào và đầu ra:
Đầu vào: Số đỉnh (V), số cạnh (E), và từng cạnh với trọng số tương ứng.
Đầu ra: Trọng số của cây khung nhỏ nhất (result).
Kết quả:
Sau khi thực thi primMST, in ra trọng số của cây khung nhỏ nhất
PRIM-SƠ ĐỒ TƯ DUY
Trang 28KRUSKAL-KHÁI NIỆM
Đặc điểm chung:
Kruskal là một thuật toán tham lam (greedy algorithm) có mục tiêu là chọn cạnh
có trọng số nhỏ nhất có thể để xây dựng cây khung nhỏ nhất.
Cách hoạt động:
Bước 1: Sắp xếp tất cả các cạnh của đồ thị theo thứ tự tăng dần của trọng số.
Bước 2: Duyệt qua từng cạnh theo thứ tự đã sắp xếp Nếu cạnh này không tạo
thành chu trình khi được thêm vào cây khung hiện tại thì thêm nó vào cây khung đó.
Bước 3: Lặp lại cho đến khi cây khung nhỏ nhất được xây dựng hoàn chỉnh (bao
gồm V-1 cạnh với V là số đỉnh của đồ thị).
Điều kiện để thực hiện Kruskal:
Đồ thị phải là đồ thị vô hướng và có thể là đồ thị có trọng số.
Đồ thị không có chu trình âm.
Trang 29vector < Edge > edges; // Danh sách các cạnh
Graph( int V , int E ) {
this ->V = V ;
this ->E = E ;
}
Hàm hợp nhất hai tập
void unite( vector < int >& parent , vector < int >& rank , int x , int y ) {
int rootX = find( parent , x );
int rootY = find( parent , y );
if ( rank [rootX] < rank [rootY]) {
parent [rootX] = rootY;
}
else if ( rank [rootX] > rank [rootY]) {
parent [rootY] = rootX;
vector < int > parent(V);
vector < int > rank(V, 0);
Trang 30parent[i] = i;
}
int result = 0; // Tổng trọng số của MST
int edgeCount = 0; // Số cạnh đã thêm vào MST
for ( const auto & edge : edges) {
if (edgeCount == V - 1) break ; // Nếu đã đủ (V-1) cạnh, thoát khỏi vòng lặp
int u = edge.u;
int v = edge.v;
int w = edge.w;
int setU = find(parent, u);
int setV = find(parent, v);
// Nếu u và v thuộc hai tập khác nhau, thêm cạnh này vào MST
vector < Edge > edges; // Danh sách các cạnh
Graph( int V , int E ) {
Trang 31}
// Hàm hợp nhất hai tập
void unite( vector < int >& parent , vector < int >& rank , int x , int y ) {
int rootX = find( parent , x );
int rootY = find( parent , y );
if ( rank [rootX] < rank [rootY]) {
parent [rootX] = rootY;
}
else if ( rank [rootX] > rank [rootY]) {
parent [rootY] = rootX;
vector < int > parent(V);
vector < int > rank(V, 0);
// Khởi tạo các tập ban đầu
for ( int i = 0; i < V; ++i) {
parent[i] = i;
}
int result = 0; // Tổng trọng số của MST
int edgeCount = 0; // Số cạnh đã thêm vào MST
for ( const auto & edge : edges) {
if (edgeCount == V - 1) break ; // Nếu đã đủ (V-1) cạnh, thoát khỏi vòng lặp
int u = edge.u;
int v = edge.v;
int w = edge.w;
int setU = find(parent, u);
int setV = find(parent, v);
// Nếu u và v thuộc hai tập khác nhau, thêm cạnh này vào MST
Trang 32cout << "dinh va canh : " ;
cin >> V >> E;
Graph graph(V, E);
cout << "Nhap so va cac canh :" << endl;
for ( int i = 0; i < E; ++i) {
int u, v, w;
cin >> u >> v >> w;
graph.addEdge(u, v, w);
}
int result = graph.kruskalMST();
cout << "Min (MST): " << result << endl;
Trang 330 - 3 với trọng số 5
0 - 1 với trọng số 10 Tổng lại bằng 19.
DIJSKTRA-KHÁI NIỆM
Các đặc điểm chính của thuật toán Dijkstra:
Loại đồ thị: Đồ thị có hướng hoặc vô hướng, có thể có trọng số
dương.
Điểm xuất phát: Một đỉnh bất kỳ trong đồ thị.
Bảng tính toán: Sử dụng bảng để lưu trữ khoảng cách ngắn
nhất từ điểm xuất phát đến các đỉnh còn lại.
Cơ chế hoạt động: Dựa trên việc mở rộng đường đi ngắn nhất
từ điểm xuất phát, sau đó cập nhật và chọn đỉnh tiếp theo để
mở rộng dựa trên khoảng cách hiện tại.
Các bước cơ bản của thuật toán:
1 Khởi tạo: Đặt khoảng cách từ điểm xuất phát đến chính nó là 0
và đặt khoảng cách từ điểm xuất phát đến các đỉnh khác là vô cùng.
2 Chọn đỉnh: Chọn đỉnh có khoảng cách ngắn nhất chưa được