Bài toán xây dựng cây khung của ựồ thị liên quan chặt chẽ ựến một số bài toán ứng dụng khác của lý thuyết ựồ thị: bài toán xây dựng tập các chu trình cơ bản của ựồ thị mà ta sẽ xét trong mục này.
Giả sử G=(V, E) là ựơn ựồ thị vô hướng liên thông, H=(V, T) là cây khung của nó. Các cạnh của ựồ thị thuộc cây khung ta sẽ gọi là các cạnh trong, còn các cạnh còn lại sẽ gọi là cạnh ngoài.
định nghĩa 7.3 Nếu thêm một cạnh ngoài e ∈ E\T vào cây khung H chúng ta sẽ thu ựược ựúng một chu trình trong H, ký hiệu chu trình này là Ce. Tập các chu trình Ω = { Ce : e ∈
E\T } ựược gọi là tập các chu trình cơ bản của ựồ thị G. Giả sử A và B là hai tập hợp, ta ựưa vào phép toán sau A ⊕ B = ( A ∪B) \ ( A ∩ B).
Tập A ⊕ B ựược gọi là hiệu ựối xứng của hai tập A và B.
Tên gọi chu trình cơ bản gắn liền với sự kiện là mỗi chu trình của ựồ thị ựều có thể thu ựược từ các chu trình cơ bản như chỉ ra trong ựịnh lý sau ựây:
định lý 7.3 Giả sử G=(V,E) là ựồ thị vô hướng liên thông, H=(V,T) là cây khung của nó. Khi ựó mọi chu trình của ựồ thị G ựiều có thể biểu diễn như là hiệu ựối xứng của một số các chu trình cơ bản.
Việc tìm tập hợp chu trình cơ bản giữ một vai trò quan trọng trong vấn ựề giải tắch mạng ựiện. Cụ thể hơn, theo mỗi chu trình cơ bản của ựồ thị tương ứng với mạng ựiện cần phân tắch ta sẽ thiết lập ựược một phương trình tuyến tắnh theo ựịnh luật Kirchoff: tổng hiệu ựiện thế dọc theo một mạch vòng là bằng không. Hệ thống phương trình tuyến tắnh thu ựược cho phép tắnh toán hiệu ựiện thế trên mọi ựường dây của lưới ựiện.
Ta sẽ xây dựng thuật toán xây dựng các chu trình cơ bản dựa trên thủ tục tìm kiếm theo chiều sâu trên ựồ thị. Thuật toán có cấu trúc tương tự như thuật toán xây dựng cây khung theo thủ tục tìm kiếm theo chiều sâu mô tả trong mục trước.
Thuật toán xây dựng tập các chu trình cơ bản.
Giả thiết rằng ựồ thị G=(V,E) ựược mô tả bằng danh sách Ke(v),v∈ V.
(* tim kiem cac chu trinh co ban cua thanh phan lien thong chua dinh v; cac bien d, num , stack, index la bien toan cuc *)
begin
d:=d+1; stack[d]:=v; num:=num+1;index[v]:=num; for u∈ Ke(v) do
if index[u]=0 then cycle(u)
else
if (u <> stack[d-1]) and (index[v]>index[u]) then <Ghi nhan chu trinh voi cac dinh:
stack[d], stack[d-1],. . ., stack[c], voi stack[c]=u> d:=d-1; end; (* Main Program *) begin for v∈ V do Index[v]:=0; num:=0; d:=0; stack[0]:=0; for v ∈ V do
if Index[v]=0 then cycle(v); end.
Chú ý: độ phức tạp tắnh toán của thuật toán vừa mô tả là O(|E| |V| ). 7.4. Thuật toán Prim
Trong trường hợp ựó thuật toán Prim tỏ ra hiệu quả hơn với những ựồ thị dày. Thuật toán Prim còn ựược gọi là phương pháp lân cận gần nhất. Trong phương pháp này bắt ựầu từ một ựỉnh tuỳ ý của ựồ thị, ựầu tiên ta nối s với ựỉnh lân cận gần nó nhất, chẳng hạn là ựỉnh y. Nghĩa là trong số các cạnh kề của ựỉnh s, cạnh (s,y) có ựộ dài nhỏ nhất.
Tiếp theo trong số các cạnh kề với hai ựỉnh s hoặc y ta tìm cạnh có ựộ dài nhỏ nhất, cạnh này dẫn ựến ựỉnh thứ ba z, và ta thu ựược cây bộ phận gồm 3 ựỉnh và 2 cạnh. Quá trình này sẽ tiếp tục cho ựến khi ta thu ựược cây gồm n ựỉnh và n-1 cạnh sẽ chắnh là cây khung nhỏ nhất cần tìm.
Giả sử ựồ thị cho bởi ma trận trọng số C = {c[i,j], i, j= 1, 2, . . . , n} . trong quá trình thực hiện thuật toán, ở mỗi bước ựể có thể nhanh chóng chọn ựỉnh và cạnh cần bổ sung vào cây khung, các ựỉnh của ựồ thị sẽ ựược gán cho các nhãn. Nhãn của một ựỉnh v sẽ gồm hai phần và có dạng [d[v], near[v]], trong ựó d[v] dùng ựể ghi nhận ựộ dài của cạnh có ựộ dài nhỏ nhất trong số các cạnh nối với ựỉnh v với các ựỉnh của cây khung ựang xây dựng (ta sẽ gọi là khoảng cách từ ựỉnh v ựến tập ựỉnh của cây khung), nói một cách chắnh xác
d[v]:= min {c[v,w] : w ∈ VH } ( = c[v,z]), còn near[v] ghi nhận ựỉnh của cây khung gần v nhất (near[v]:=z).
Thuật toán Prim ựược mô tả ựầy ựủ trong thủ tục sau:
Procedure Prim; Begin
(* buoc khoi tao *)
chon s la mot dinh nao do cua do thi; VH:={s} ; T:=∅ ; d[s]:=0; near[s]:=s. For v∈ V\VH do Begin D[v]:=c[s,v]; near[v]:=s; End; (* buoc lap *) stop:=false; while not stop do
begin
tim u ∈ V\VH thoa man:
d[u] =min {d[v]: u∈ V\VH } ; VH:= VH ∪ {u} ; T := T ∪ {(u, near[u])} ; If |VH| = n then
Begin
H=( VH,T) la cay khung nho nhat cua do thi; Stop:=true; End Else For v∈ V\ VH do If d[v]>c[u,v] then Begin d[v]:=c[u,v]; near[v]:=u; End; end; End;
Ngoài ra, ta có thể thực hiện cũng theo ý tưởng này(chi tiết trong giáo trình bài tập)
Hình 7.3 đồ thị và cây khung nhỏ nhất. Ma trận trọng số của ựồ thị có dạng 1 2 3 4 5 6 1 0 33 17 ∞ ∞ ∞ 2 33 0 18 20 ∞ ∞ C = 3 17 18 0 16 4 ∞ 4 ∞ 20 16 0 9 8 5 ∞ ∞ 4 9 0 14 6 ∞ ∞ ∞ 8 14 0
Bảng dưới ựây ghi nhãn của các ựỉnh trong các bước lặp của thuật toán, ựỉnh ựánh dấu * là ựỉnh ựược chọn ựể bổ sung vào cây khung (khi ựó nhãn của nó không còn bị biến ựổi trong các bước lặp tiếp theo, vì vậy ta ựánh dấu Ờ ựể ghi nhận ựiều ựó):
Bước lặp đỉnh 1 đỉnh 2 đỉnh 3 đỉnh 4 đỉnh 5 đỉnh 6 VH T Khởi tạo [0,1] [33,1] [17,1]* [∞ ,1] [∞ ,1] [∞ ,1] 1 ∅ 1 - [18,3] - [16,3] [4,3]* [∞ ,1] 1,3 (3,1) 2 - [18,3] - [9,5] - [14,5] 1,3,5 (3,1), (5,3)
3 - [18,3] - - - [8,4] 1,3,5,4 (3,1), (5,3), (4,5)
4 - [18,3]* - - - - 1,3,5,4,6 (3,1), (5,3), (4,5), (6,4)
5 - - - - - - 1,3,5,4,6,2 (3,1), 5,3), (4,5), 6,4), (2,3)
7.5 Thuật toán Kruskal
Thuật toán Kruskal làm việc kém hiệu quả với những ựồ thị dày (ựồ thị với số cạnh m ≈ n(n-1)/2), nhưng sẽ là hiệu quả hơn với những ựồ thị mỏng(ắt cạnh so với số ựỉnh). Thuật toán sẽ xây dựng tập cạnh T của cây khung nhỏ nhất H=(V,T) theo từng bước. Trước hết sắp xếp các cạnh của ựồ thị G theo thứ tự không giảm của ựộ dài. Bắt ựầu từ tập T=∅ , ở mỗi bước ta sẽ lần lượt duyệt trong danh sách cạnh ựã sắp xếp, từ cạnh có ựộ dài nhỏ ựến cạnh có ựộ dài lớn hơn, ựể tìm ra cạnh mà việc bổ sung nó vào tập T gồm n-1 cạnh. Cụ thể, thuật toán có thể mô tả như sau:
Procedure Kruskal; Begin
T:=∅;
While |T| < (n-1) and (E<>∅) do Begin
E:=E \ {e};
if (T ∪ {e} không chứa chu trình) then T:= T ∪ {e} ; End;
if (|T| < n-1) then đồ thị không liên thông; End;
Vắ dụ 7.4 Tìm cây khung nhỏ nhất của ựồ thị cho trong hình 7.3 dưới.
Bước khởi tạo. đặt T:=∅ . Sắp xếp các cạnh của ựồ thị theo thứ tự không giảm của ựộ dài ta có dãy:
(3,5) , (4,6) , (4,5) , (5,6) , (3,4) , (1,3) , (2,3) , (2,4) , (1,2) dãy ựộ dài tương ứng của chúng
4, 8, 9, 14, 16, 17, 18, 20, 23.
Ở ba lần gặp ựầu tiên ta lần lượt bổ sung vào tập T các cạnh (3,5) , (4,6) , (4,5). Rõ ràng nếu thêm cạnh (5,6) vào T thì sẽ tạo thành 2 cạnh (4,5), (4,6) ựã có trong T chu trình. Tình huống tương tự cũng xảy ra ựối với cạnh (3,4) là cạnh tiếp theo của dãy. Tiếp theo ta bổ sung cạnh (1,3), (2,3) vào T và thu ựược tập T gồm 5 cạnh:
T = { (3,5) , (4,6) , (4,5) , (1,3) , (2,3) } Chắnh là tập cạnh của cây khung nhỏ nhất cần tìm.
Chứng minh tắnh ựúng ựắn của thuật toán.
Rõ ràng ựồ thị thu ựược theo thuật toán có n-1 cạnh và không có chu trình, vì vậy theo ựịnh lý 7.1 nó là cây khung của ựồ thị G. Như vậy, chỉ còn phải chỉ ra rằng T có ựộ dài nhỏ nhất. Giả sử tồn tại cây S của ựồ thị G mà c(S) < c(T). Ký hiệu ek là cạnh ựầu tiên trong dãy các cạnh của T xây dựng theo thuật toán vừa mô tả không thuộc S. khi ựó ựồ thị con của G sinh bởi cây S ựược bổ sung cạnh ek sẽ chứa một chu trình C duy nhất ựi qua ek. Do chu trình C phải chứa cạnh e thuộc S nhưng không thuộc T nên ựồ thị con thu ựược từ S bằng cách thay cạnh e của nó bởi cạnh ek (ký hiệu ựồ thị là SỖ) sẽ là cây khung. Theo cách xây dựng c(ek) ≤ c(e) do ựó c(SỖ) ≤ c(S), ựồng thời số cạnh chung của SỖ và T ựã tăng thêm 1 so với số cạnh chung của S và T. Lặp lại quá trình trên từng bước một ta có thể biến ựổi S thành T và trong mỗi bước tổng ựộ dài không tăng, tức là c(T) ≤ c(S). Mâu thuẫn thu ựược chúng tỏ T là cây khung nhỏ nhất.
Về việc lập trình thực hiện thuật toán.
Khối lượng tắnh toán nhiều nhất của thuật toán chắnh là ở bước sắp xếp các cạnh của ựồ thị theo thứ tự không giảm của ựộ dài ựể lựa chọn cạnh bổ sung. đối với ựồ thị m cạnh cần phải thực hiện mlogm phép toán ựể sắp xếp các cạnh của ựồ thị thành dãy không giảm theo ựộ dài. Tuy nhiên, ựể xây dựng cây khung nhỏ nhất với n-1 cạnh, nói chung ta không cần phải sắp thứ tự toàn bộ các cạnh mà chỉ cần xét phần trên của dãy ựó chứa r <
m cạnh. để làm việc ựó ta có thể sử dụng các thủ tục sắp xếp dạng Vun ựống (Heap Sort). Trong thủ tục này, ựể tạo ựống ựầu tiên ta mất cỡ O(m) phép toán, mỗi phần tử tiếp theo trong ựống có thể tìm sau thời gian O(log m). Vì vậy, với cải tiến này thuật toán sẽ mất thời gian cỡ O(m+p) log m) cho việc sắp xếp các cạnh. Trong thực tế tắnh toán số p nhỏ hơn rất nhiều so với m.
Vấn ựề thứ hai trong việc thể hiện thuật toán Kruskal là việc lựa chọn cạnh ựể bổ sung ựòi hỏi phải có một thủ tục hiệu quả kiểm tra tập cạnh T ∪ {e} có chứa chu trình hay không. để ý rằng, các cạnh trong T ở các bước lặp trung gian sẽ tạo thành một rừng. Cạnh e cần khảo sát sẽ tạo thành chu trình với các cạnh trong T khi và chỉ khi cả hai ựỉnh ựầu của nó thuộc vào cùng một cây con của rừng nói trên. Do ựó, nếu cạnh e không tạo thành chu trình với các cạnh trong T, thì nó phải nối hai cây khác nhau trong T. vì thế, ựể kiểm tra xem có thể bổ sung cạnh e vào T ta chỉ cần kiểm tra xem nó có nối hai cây khác nhau trong T hay không. Một trong các phương pháp hiệu quả ựể thực hiện việc kiểm tra này là ta sẽ phân hoạch tập các ựỉnh của ựồ thị ra thành các tập con không giao nhau, mỗi tập xác ựịnh bởi một cây con trong T(ựược hình thành ở các bước do việc bổ sung cạnh vào T). chẳng hạn, ựối với ựồ thị trong vắ dụ 3, ựầu tiên ta có sáu tập con 1 phần tử: {1}, {2}, {3}, {4}, {5}, {6} . Sau khi bổ sung cạnh (3, 5), ta có các tập con {1}, {2}, {3,5}, {4}, {6} . Ở bước thứ 3, ta chọn cạnh (4, 5), khi ựó hai tập con ựược nối lại và danh sách các tập con là {1}, {2} , {3, 5, 4, 6} . Cạnh có ựộ dài tiếp theo là (4,6), do hai ựầu của nó thuộc vào cùng một tập con {3,4,5,6} , nên nó sẽ tạo thành chu trình trong tập này. Vì vậy cạnh này không ựược chọn. Và thuật toán sẽ tiếp tục chọn cạnh tiếp theo ựể khảo sát Ầ
Như vậy, ựể giải quyết vấn ựề thứ hai này ta phải xây dựng hai thủ tục: Kiểm tra xem hai ựầu u, v của cạnh e=(u,v) có thuộc vào hai tập con khác nhau hay không, và trong trường hợp câu trả lời là khẳng ựịnh, nối hai tập con tương ứng thành một tập. Chú ý rằng mỗi tập con trong phân hoạch có thể lưu trữ như là một cây có gốc, và khi ựó mỗi gốc sẽ ựược sử dụng làm nhãn nhận biết tập con tương ứng.
Bài 8 Thảo luận về cài ựặt thuật toán tìm cây khung nhỏ nhất trên ựồ thị
8.1. Cài ựặt xây dựng tập các chu trình cơ bản của ựồ thị
x1 a1 x2 a2 x3
a7 a5 a3
x6 a6 x5 a4 x4
a8 a10
x7 a9 x8
Hình 8.1 Hệ chu trình ựộc lập cho ựồ thị vô hướng G
α1>< x1 a1 x2 a2 x3 a3 x4 a4 x5 a6 x6 a7 x1 α2>< x1 a1 x1 a7 x6 a6 x5 a5 x2
α3>< x2 a2 x2 a3 x4 a4 x5 a5 x2
α4>< x6 a7 x1 a1 x2 a2 x3 a3 x4 a10 x8 a9 x7 a8 x6
H={α 1 , α2 , α3 , α4 } . Hệ véctơ ựặc trưng hãy xác ựịnh vectơ ựặc trưng cho mỗi chu trình .
định hướng tùy ý vào cạnh của G. Ta nhận ựược ựồ thị có hướng G1 .
x1 a1 x2 a2 x3
a7 a5 a3
x6 a6 x5 a4 x4
a8 a10
x7 a9 x8
Hình 8.2 Hệ chu trình ựộc lập cho ựồ thị có hướng G1
vt a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 α1 v1 +1 +1 -1 +1 0 -1 +1 0 0 0 α 2 v2 -1 0 0 0 +1 +1 -1 0 0 0 α 3 v3 0 +1 -1 +1 +1 0 0 0 0 0 α 4 v4 +1 +1 -1 0 0 0 +1 -1 +1 -1 v5 +1+1-1 -1 -1 -1 +1 +1 -1 -1 -1-1 -2 -1 -1 +1 +1 0 0 0 0 0 0
Chiều dương +1 không có mặt 0 âm -1
(α 5)v5>< x1 a1 x2 a5 x5 a6 x6 a7 x1 a1 x2 a5 x5 a4 x4 a3 x3 a2 x2 a1 x1
để xác ựịnh vectơ ựặc trưng của mỗi chu trình trong ựồ thị G chúng ta phải tiến hành theo 2 bước:
B1:Gắn ựịnh hướng cho mỗi cạnh của ựồ thị G ựể ựược 1 ựồ thị có hướng G1 B2:Xác ựịnh vectơ ựặc trưng
Giả sử ựồ thị G = (X,E) có tập cạnh {e1 , e2 ,Ầ,em}
Trong ựồ thị có hướng G1 ta vẫn sử dụng e1 ựể kắ hiệu cùng tương ứng với cạnh ei trong ựồ thị G
Giả sử có chu trình α ta sử dụng v(α) ựể kắ hiệu vectơ ựặc trưng của chu trình α. Vì ựồ thị G có m cạnh nên vectơ ựặc trưng v(α) = {v1,v2,Ầ..,vi,v1+1;Ầ.vm}
Trong ựó : vi = ti - si , ti = số lần α qua ei theo chiều dương trong ựồ thị G1 si = số lần α qua ei theo chiều âm.
Hệ chu trình ựộc lập:
H = {α 1 , α2 , Ầ. , αn}. có hệ vectơ ựặc trưng. K = {v(α1) , v(α2) ,Ầ..v(αn) }
định nghĩa: + Hệ H trong chu trình G ựược gọi là hệ chu trình ựộc lập nếu hệ vectơ ựặc trưng K là hệ vectơ ựộc lập tuyến tắnh.
+Hệ chu trình H ựược gọi là hệ chu trình cực ựại hay hệ chu trình cơ sở trong chu trình G nếu hệ vectơ ựặc trưng K là hệ vectơ ựộc lập tuyến tắnh cực ựại
8.2. Cài ựặt thuật toán Prim
Tìm cây bao trùm nhỏ nhất của ựồ thị G dùng thuật toán Prim
Thuật toán Prim còn ựược gọi là phương pháp lân cận gần nhất: đưa dần các ựỉnh vào cây khung. Mỗi lần, chọn một ựỉnh (chưa xét) là ựỉnh kề gần nhất (trọng số nhỏ nhất) với một trong các ựỉnh ựã ựược ựưa vào cây khung.
Nghĩa là:
Ớ đầu tiên ựưa một ựỉnh tuỳ ý vào cây khung.
Ớ Lần lượt ựưa nỜ1 cạnh vào cây khung bằng cách : mỗi lần chọn một cạnh có trong số nhỏ nhất sao cho cạnh ựó chỉ có một ựỉnh ựã thuộc cây khung. Thuật toán ựược trình bày chi tiết trong bài 7. Chương trình ựược minh họa trong bài 8- giáo trình bài tập. Sau ựây là 1 vắ dụ:
A B C E D F G 5 7 8 5 7 9 15 6 8 11 9 chọn cạnh AD có trọng số nhỏ nhất (5) và có ựỉnh A thuộc cây khung
A B C E D F G 5 7 8 5 7 9 15 6 8 11 9 chọn cạnh AF có trọng số nhỏ nhất (6) và có ựỉnh D thuộc cây khung
A B C E D F G 5 7 8 5 7 9 15 6 8 11 9 chọn cạnh AB có trọng số nhỏ nhất (7) A B C E D F G 5 7 8 5 7 9 15 6 8 11 9 chọn cạnh BE có trọng số nhỏ nhất (7)
Trang 60
Hình 8.3 Minh họa từng bước thuật toán Prim tìm cây khung nhỏ nhất
8.3. Cài ựặt thuật toán Kruskal
Tìm cây bao trùm nhỏ nhất của ựồ thị G dùng thuật toán Kruskal
Thuật toán Kruskal xây dựng cây khung nhỏ nhất bằng cách hợp nhất dần các cây :
Cho ựồ thị trọng số G=(V,E) liên thông có n ựỉnh. đặt T=( V,∅ ) không chứa cạnh nào. Có thể xem T gồm n cây, mỗi cây chỉ có một ựỉnh. Sau ựó, lần lượt xét các cạnh của