đỉnh trong cùng một miền liên thông có đường đi trực tiếp/gián tiếp đến nhau; giữa các đỉnh khác miền liên thông không tồn tại đường đi này.. THU ẬT TOÁN TÌM SỐ THÀNH PHẦN LIÊN THÔNG Bư
Trang 2M ỤC LỤC
Chương 1: Biểu diễn đồ thị trên máy tính 3
Chương 2: Liên thông và các thành phần liên thông 8
Chương 3: Cây và cây bao trùm 12
Chương 4: Thuật toán Prim 14
Chương 5: Cây bao trùm nhỏ nhất & Thuật toán Kruskal 16
Chương 6: Tìm đường trong đồ thị 18
Chương 7: Tìm đường trong đồ thị - Thuật toán Dijkstra 20
Chương 8: Tìm đường trong đồ thị - Thuật toán Floyd 22
Trang 3Chương 1: Biểu diễn đồ thị trên máy tính
I NH ẮC LẠI LÝ THUYẾT
1 MA TR ẬN KỀ (ADJACENCY MATRIX)
- Chi phí duyệt các cạnh kề của đỉnh i luôn luôn cố định là
Trang 42 DANH SÁCH C ẠNH (EDGE LIST)
Trong trường hợp đồ thị có n đỉnh, m cạnh, ta có thể biểu diễn đồ thị dưới dạng danh
3 DANH SÁCH K Ề (ADJACENCY LIST)
Để khắc phục nhược điểm của phương pháp biểu diễn đồ thị bằng ma trận kề và danh
chứa các đỉnh kề với nó
Ví d ụ
Trang 5Danh sách kề tương ứng
Hoặc:
Các tính ch ất của danh sách kề
Ưu điểm:
- Tiết kiệm chi phí lưu trữ
Trang 6T ổ chức dữ liệu
Để biểu diễn ma trận, ta dùng một mảng hai chiều kiểu nguyên
#define MAX 100 struct GRAPH { int n;
int a[MAX][MAX];
};
Đọc ma trận kề từ tập tin vào mảng
void ReadGraph(GRAPH &g, char *fn) {
// m ở file, nếu không mở được file sẽ báo lỗi và thoát FILE * f = fopen(fn, “rt”);
if (f == NULL) {
exit(0);
} // đọc giá trị đỉnh của đồ thị vào biến n fscanf(f, “%d”, &g.n);
// đọc giá trị của ma trận a từ file int i, j;
for (i=0; i<g.n; i++)
for (j=0; j<g.n; j++)
fscanf(f, “%d”, &g.a[i][j]);
// đóng file nhập fclose(f);
}
Xu ất ma trận kề ra màn hình
void PrintGraph(GRAPH &g) {
// in ra s ố đỉnh của đồ thị printf(“%d\n”, g.n);
for (int i=0; i<g.n; i++) { for (int j=0; j<g.n; j++)
printf(“%d\t”, g.a[i][j]);
printf(“\n”);
} }
2 CÀI ĐẶT DANH SÁCH CẠNH VÀ DANH SÁCH KỀ
Xem như bài tập
Trang 7III BÀI T ẬP
định dạng ở phần 2 In ra màn hình các giá trị sau đây:
G có hướng hay vô hướng
có tồn tại chu trình Euler hay không? Nếu có, hãy chỉ ra một chu trình Euler của đồ thị này
định dạng ở phần 2 In ra tập tin GRAPH.EL theo định dạnh danh sách cạnh
theo định dạng ở phần 2 In ra tập tin GRAPH.AM theo định dạnh ma trận kề
định dạng ở phần 2 In ra tập tin GRAPH.AL theo định dạnh danh sách kề
định dạng ở phần 2 In ra tập tin GRAPH.AM theo định dạnh ma trận kề
họa
Trang 8Chương 2: Liên thông và các thành phần liên thông
I NH ẮC LẠI LÝ THUYẾT
1 ĐỒ THỊ LIÊN THÔNG
Đối với đồ thị có hướng, ta có hai khái niệm liên thông mạnh và liên thông yếu Việc
kiểm tra một đồ thị có hướng có liên thông yếu hay không về độ phức tạp tương đương với việc kiểm tra một đồ thị vô hướng có liên thông hay không Bài hướng dẫn
hay không
đỉnh trong cùng một miền liên thông có đường đi trực tiếp/gián tiếp đến nhau; giữa các đỉnh khác miền liên thông không tồn tại đường đi này) Như vậy, một đồ thị liên thông là một đồ thị chỉ có duy nhất một miền liên thông
2 THU ẬT TOÁN TÌM SỐ THÀNH PHẦN LIÊN THÔNG
Bước 1: Đánh dấu tất cả các đỉnh trong đồ thị là chưa duyệt Biến đếm số thành phần
liên thông được gán là 0
Bước 2: Chọn một đỉnh i bất kỳ chưa được duyệt, sử dụng một hàm Visit() để duyệt
đỉnh i và tất cả các đỉnh j có nối với đỉnh i Trong quá trình duyệt, ta sẽ đánh dấu các
đỉnh này là đã được duyệt để sau này không xét trở lại nữa Kết thúc lần duyệt này, ta được một miền liên thông
Để có thể dễ dàng in lại từng thành phần liên thông, ta sử dụng nhãn đánh dấu cho
Bước 3: Tăng biến đếm số thành phần liên thông
Bước 4: Nếu số thành phần liên thông là 1, in ra đồ thị liên thông
Ngược lại, in ra số thành phần liên thông
II CÀI ĐẶT
// nhãn cho các đỉnh, 0 là chưa duyệt
Trang 9// 1, 2, … là đã duyệt và đỉnh thuộc miền liên thông tương ứng
int visited[MAX];
int nconnect;
void solve(GRAPH& g) {
// kh ởi tạo nhãn cho tất cả các đỉnh là chưa duyệt
// đặt số miền liên thông ban đầu la 0
if (visited[i] == 0) { visit(g, i, nconnect);
nconnect++;
} }
void visit(GRAPH& g, int i, int nconnect) {
// gán nhãn nconnect cho đỉnh i visited[i] = nconnect;
// duy ệt các đỉnh j chưa được duyệt và có nối với i
III BÀI T ẬP
phần liên thông
chỉ cách thêm vào G số cạnh tối thiểu để G liên thông
Trang 10tấm ảnh nhị phân Một đối tượng được xác định gồm các điểm đen liên thông bốn với
Dữ liệu: Dữ liệu vào từ file văn bản BITMAP.INP
chiều dài và chiều rộng của tấm ảnh
Kết quả: Kết quả ghi ra file văn bản BITMAP.OUT như sau:
làm việc riêng của mình Do nhu cầu công việc , hằng ngày mỗi nhân viên có thể phải tiếp xúc với một số nhân viên khác Vào một ngày làm việc bình thường , có một nhân viên bị nhiễm SARS , nhưng do không biết nên người này vẫn đi làm Đến cuối ngày làm việc người ta mới phát hiện ra người nhiễm bệnh SARS đầu tiên Khả năng lây lan của SARS rất nhanh chóng : một người nhiễm bệnh SARS nếu tiếp xúc với một người khác có thể sẽ truyền bệnh cho người này
Yêu cầu: Hãy giúp các bác sĩ kiểm tra xem cuối ngày hôm đó , có bao nhiêu người có
thể nhiễm bệnh và đó là những người nào để còn cách ly Người có tiếp xúc với người nhiễm bệnh được coi là người nhiễm bệnh
Dữ liệu: Dữ liệu vào từ file văn bản SARS.INP
Trang 11- Dòng đầu tiên ghi 2 số tự nhiên N và K (1 < N <=250, 1<=K<=N) tương ứng số lượng người làm việc trong tòa nhà và số hiệu của nhân viên đã nhiễn SARS đ ầu tiên
người thứ I theo cách sau : số đầu tiên J của dòng là tổng số nhân viên đã gặp người thứ I, tiếp theo là J số tự nhiên lần lượt là số hiệ u của các nhân viên đó Nếu J=0 có nghĩa là không ai đã tiếp xúc với người I
Kết quả: Kết quả ghi ra file văn bản SARS.OUT như sau:
sách cần được sắp xếp theo thứ tự tăng dần của số hiệu nhân viên
Trong các file dữ liệu và kết quả , các số trên cùng một dòng cách nhau ít nhất một dấu cách
Trang 12Chương 3: Cây và cây bao trùm
I NH ẮC LẠI LÝ THUYẾT
Cây là đồ thị vô hướng, liên thông và không có chu trình đơn Cây bao trùm của đồ thị vô hướng liên thông G là cây chứa tất cả các đỉnh của G Cây bao trùm của đồ thị có thể được xác định bằng các cách sau:
• Xác định cây bao trùm bằng thuật toán hợp nhất: Xuất phát từ tập T chứa n đỉnh của
G và không chứa cạnh nào, ta lần lượt thêm dần các cạnh trong G vào T sao cho không tạo ra chu trình Đến khi đạt n-1 cạnh, T là cây bao trùm của G
• Xác định cây bao trùm bằng thuật toán tìm kiếm: Áp dụng thuật giải duyệt đồ thị,
II CÀI ĐẶT
từ đỉnh i và previous[i] chính là cây bao trùm cần tìm
III BÀI T ẬP
rìa lâu đài, do v ậy, nếu thoát ra được rìa lâu đài thì coi như đã thoát hi ểm Để nguỵ trang, người ta cho đào nhiều nhánh hầm cụt và cửa vào giả Ngoài ra, để tăng khả năng thoát hiểm, người ta còn xây dựng các đường hầm giao nhau tại một số vị trí Để
(ô ở góc trên trái có toạ độ (0, 0)) 2 ô chỉ có thể thông nhau nếu chúng có chung cạnh
Trang 13Dữ liệu nhập vào từ tập tin văn bản DUONGHAM.IN gồm:
trung tâm)
• N dòng tiếp theo, mỗi dòng chứa n số là các số ở các vị trí tương ứng trên hoạ đồ
phải đi qua theo đúng trình tự của một cách thoát hiểm
Trang 14Chương 4: Thuật toán Prim
I NH ẮC LẠI LÝ THUYẾT
duyệt đồ thị đã đề cập ở bài 3, các bước thực hiện của thuật toán như sau:
Bước 1: Đánh dấu tất cả các đỉnh của đồ thị là chưa duyệt
Bước 2: Chọn một đỉnh bất kỳ làm gốc của cây bao trùm, để thống nhất ta chọn đỉnh 0, đánh
dấu đỉnh này là đã duyệt
Bước 3: Chọn cạnh (x, y) có trọng số nhỏ nhất nối giữa vùng đỉnh đã duyệt và vùng đỉnh
chưa duyệt (x được đánh dấu là đã duyệt và y được đánh dấu là chưa duyệt) Nếu không tìm được một cạnh nào như thế, dừng thuật toán
Bước 4: Cập nhật cạnh (x, y) vào cây bao trùm
Bước 5: Đánh dấu đỉnh y là đã duyệt
Bước 6: Quay lại bước 3
Trang 15dụng PriorityQueue
Trang 16Chương 5: Cây bao trùm nhỏ nhất & Thuật toán Kruskal
I NH ẮC LẠI LÝ THUYẾT
đề cập ở bài 3, các bước chi tiết của thuật toán như sau:
Bước 1: Tạo danh sách cạnh edges Đánh dấu tất cả các cạnh là chưa duyệt Bước 2: Sắp xếp lại các cạnh theo thứ tự trọng số tăng dần
Bước 3: Khởi tạo nhãn root, với root[i] = i (root[i] biểu diễn đỉnh gốc của i
trong cây chứa i)
Bước 4: Chọn ra cạnh e = (x,y) nhỏ nhất chưa duyệt trong danh sách cạnh
Bước 5: Đánh dấu cạnh e là đã duyệt Nếu nhãn của root[e.x] và root[e.y]
làm phát sinh chu trình), quay lại bước 4
Bước 6: Cập nhật cạnh e vào cây bao trùm, nếu số cạnh đã chọn n-1, dừng
int i = edge[k][i];
int j = edge[k][j];
// ki ểm tra khi thêm cạnh nào vào cây có tạo ra chu trình
Trang 17int rj = j; while (root[rj] != -1) rj = root[rj];
này
khi xử lí thuật toán
Trang 18Chương 6: Tìm đường trong đồ thị
I NH ẮC LẠI LÝ THUYẾT
cung đi qua)
cụ thể, ta phải thực hiện lại việc lưu vết (lưu đỉnh cha)
Để tìm đường trong đồ thị, ta có thể sử dụng phương pháp duyệt đồ thị theo chiều sâu
thông đã nêu ở chương 2) Nếu cài đặt bằng phương pháp khử đệ quy, điểm khác biệt
stackcount = 1; // ch ỉ có một phần tử trong ngăn xếp
// duy ệt tất cả các đỉnh trong ngăn xếp
while (stackcount > 0) {
// đưa j vào ngăn xếp
Trang 19stackcount++;
// gán đường đi cho j
previous[j] = i;
… }
} }
// duy ệt tất cả các đỉnh trong hàng đợi
while (queueindex < queuecount) {
// đưa j vào hàng đợi
} }
III BÀI T ẬP
đường đi từ đỉnh I đến đỉnh J bất kì nhập từ bàn phím sử dụng thuật toán tìm kiếm theo chiều sâu
đường đi từ đỉnh I đến đỉnh J bất kì nhập từ bàn phím sử dụng thuật toán tìm kiếm theo chiều rộng
một phần tử trong đó)
Trang 20Chương 7: Tìm đường trong đồ thị - Thuật toán Dijkstra
I NH ẮC LẠI LÝ THUYẾT
Thuật toán Dijkstra có thể mô tả như sau:
nguồn s đến một đỉnh u nào đó thuộc S, rồi đi theo cạnh nối u-v
Trong các đỉnh ngoài S, chúng ta chọn đỉnh u có nhãn d[u] bé nhất, bổ sung vào tập S
hợp với định nghĩa
đi ngắn nhất đến một đỉnh đích t, thì chúng ta dừng lại khi đỉnh t được bổ sung vào tập S
2 Cài đặt Dijkstra sử dụng Heap
ởi tạo tập S chỉ chứa đỉnh ban đầu s;
Trang 21for (mỗi đỉnh v thuộc G) { D[v] = C(s, v);
} D[s] = 0;
}
III BÀI T ẬP
1 Cài đặt thuật toán Dijkstra
G quen nhau đã lâu Không bi ết từ lúc nào cả hai đã tuân thủ một qui ước: mỗi chiều
đi học về cần gặp nhau tại một địa điểm do G chọn (có thể là rạp chiếu bóng, quán kem hay đơn giản chỉ là góc phố quen) sau đó về thẳng nhà của mỗi người Nhiệm vụ
sau đó về nhà – dĩ nhiên, B và M đang học 2 trường khác nhau và ở 2 nhà cũng khác
đánh số từ 1 đến N, trường NAM là địa điểm U, trường NỮ là địa điểm V, nhà B là địa điểm Z, nhà G là địa điểm T, điểm hẹn là địa điểm X B cũng đã kh ảo sát và ghi
điểm không có đường nối trực tiếp Bạn hãy giúp B lập trình để có thể xác định đường
đi cho B và M sao cho tổng quãng đư ờng cả hai người phải đi qua là bé nhất – vì B chưa học LTĐT
- Dòng đầu tiên ghi số nguyên N là số địa điểm của thành phố A (N <= 100)
cách đoạn đường nối từ I đến J và được ghi là 0 nếu không có đường nối
- Dòng cuối cùng ghi 5 số U, V, Z, T, X
Trang 22Chương 8: Tìm đường trong đồ thị - Thuật toán Floyd
I NH ẮC LẠI LÝ THUYẾT
Cho đơn đồ thị có hướng, có trọng số G=(V,E) với n đỉnh và m cạnh, Ma trận trọng số C[u,v] Bài toán đặt ra là tính tất cả các d(u,v) là khoảng cách nhỏ nhất từ u đến v
tính lại các C[u,v] thành đường đi ngắn nhất từ u đến v theo công thức:
C[u,v] := min (C[u,v], C[u,k] C[k,v]) với mọi đỉnh k xét từ 1 đến n
đến v thì ta ghi nhận lại đường đi ngắn nhất (hiện có) là đường đi qua k
int **Cost; Cost = new int *[g.n];
for (i = 0; i < gr.nV; i++) Cost[i] = new int [g.n];
int **Previous; Previous = new int *[g.n];
for (i = 0; i < g.n; i++) Previous[i] = new int [g.n];
for (j = 0; j < g.n; j++)
if (Cost[i][j] > Cost[i][k] + Cost[k][j]){
Cost[i][j] = Cost[i][k] + Cost[k][j];
Previous[i][j] = Previous[k][j];
} }
ẬP
Trang 231 Cài đặt thuật toán Floyd
tới các đỉnh còn lại là ngắn nhất
quanh điểm O sao cho chu vi hàng rào là nhỏ nhất (O không nằm trên cạnh của hàng rào)