Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Bài toán cây khung nhỏ nhất trong đồ thị
1. Trường THPT chuyên Tiền Giang Lớp 11 Tin
BÀI TOÁN TÌM CÂY KHUNG CÓ TRỌNG SỐ NHỎ NHẤT
I - BÀI TOÁN
Cho G = (V, E) là đồ thị vô hướng liên thông có trọng số, với một cây khung T của G, ta gọi trọng số của cây
T là tổng trọng số các cạnh trong T. Bài toán đặt ra là trong số các cây khung của G, chỉ ra cây khung có
trọng số nhỏ nhất, cây khung như vậy được gọi là cây khung nhỏ nhất của đồ thị (minimum spanning tree) và
bài toán đó gọi là bài toán cây khung nhỏ nhất. Sau đây ta sẽ xét hai thuật toán thông dụng để giải bài toán
cây khung nhỏ nhất của đơn đồ thị vô hướng có trọng số. Bạn có thể đưa vào một số sửa đổi nhỏ trong thủ
tục nhập liệu để giải quyết bài toán trong trường hợp đa đồ thị.
Input: file văn bản MINTREE.INP:
- Dòng 1: Ghi hai số số đỉnh n (≤ 1000) và số cạnh m của đồ thị cách nhau ít nhất 1 dấu cách
- m dòng tiếp theo, mỗi dòng có dạng 3 số u, v, c[u, v] cách nhau ít nhất 1 dấu cách thể hiện đồ thị có
cạnh (u, v) và trọng số cạnh đó là c[u, v]. (c[u, v] là số nguyên có giá trị tuyệt đối không quá 1000).
Output: file văn bản MINTREE.OUT ghi các cạnh thuộc cây khung và trọng số cây khung.
II. THUẬT TOÁN KRUSKAL
a) Ý tưởng:
Nạp dần các cạnh ngắn nhất vào cây khung nếu cạnh ấy không tạo thành chu trình với các cạnh đã
nạp.
Các bước cụ thể:
- Sắp xếp các cạnh theo trọng số tăng dần.
- Lần lượt kết nạp các cạnh có trọng số nhỏ nhất trong các cạnh còn lại vào cây nếu sau khi
kết nạp các cạnh này không tạo thành chu trình. Quá trình hợp nhất dừng lại khi cây có đủ
N-1 cạnh.
b) Mô hình thuật toán Kruskal:
for ∀k∈V do Lab[k] := -1;
for (∀(u, v) ∈ E theo thứ tự từ cạnh trọng số nhỏ tới cạnh trọng số lớn) do
begin
r1 := GetRoot(u); r2 := GetRoot(v);
if r1 ≠ r2 then {(u, v) nối hai cây khác nhau}
begin
<Kết nạp (u, v) vào cây, nếu đã đủ n - 1 cạnh thì thuật toán dừng>
Union(r1, r2); {Hợp nhất hai cây lại thành một cây}
end;
end;
Chuyên đề: Tìm cây khung có trọng số nhỏ nhất 1
2. Trường THPT chuyên Tiền Giang Lớp 11 Tin
c) Chương trình:
{$MODE DELPHI} (*This program uses 32-bit Integer [-231..231 - 1]*)
program Finding_the_Minimum_Spanning_Tree_using_Kruskal_Algorithm;
const
InputFile = 'MINTREE.INP';
OutputFile = 'MINTREE.OUT';
maxV = 1000;
maxE = (maxV - 1) * maxV div 2;
type
TEdge = record {Cấu trúc một cạnh}
u, v, c: Integer; {Hai đỉnh và trọng số}
Mark: Boolean; {Đánh dấu có được kết nạp vào cây khung hay không}
end;
var
e: array[1..maxE] of TEdge; {Danh sách cạnh}
Lab: array[1..maxV] of Integer; {Lab[v] là đỉnh cha của v, nếu v là gốc thì Lab[v] = - số con cây
gốc v}
n, m: Integer;
Connected: Boolean;
procedure LoadGraph;
var
i: Integer;
f: Text;
begin
Assign(f, InputFile); Reset(f);
ReadLn(f, n, m);
for i := 1 to m do
with e[i] do
ReadLn(f, u, v, c);
Close(f);
end;
procedure Init;
var
i: Integer;
begin
for i := 1 to n do Lab[i] := -1; {Rừng ban đầu, mọi đỉnh là gốc của cây gồm đúng một nút}
for i := 1 to m do e[i].Mark := False;
end;
function GetRoot(v: Integer): Integer; {Lấy gốc của cây chứa v}
begin
while Lab[v] > 0 do v := Lab[v];
GetRoot := v;
end;
procedure Union(r1, r2: Integer); {Hợp nhất hai cây lại thành một cây}
var
x: Integer;
begin
x := Lab[r1] + Lab[r2];
if Lab[r1] > Lab[r2] then
begin
Lab[r1] := r2;
Lab[r2] := x;
end
else
begin
Lab[r1] := x;
Lab[r2] := r1;
end;
end;
procedure AdjustHeap(root, last: Integer); {Vun thành đống, dùng cho HeapSort}
var
Key: TEdge;
Chuyên đề: Tìm cây khung có trọng số nhỏ nhất 2
3. Trường THPT chuyên Tiền Giang Lớp 11 Tin
child: Integer;
begin
Key := e[root];
while root * 2 <= Last do
begin
child := root * 2;
if (child < Last) and (e[child + 1].c < e[child].c)
then Inc(child);
if Key.c <= e[child].c then Break;
e[root] := e[child];
root := child;
end;
e[root] := Key;
end;
procedure Kruskal;
var
i, r1, r2, Count, a: Integer;
tmp: TEdge;
begin
Count := 0;
Connected := False;
for i := m div 2 downto 1 do AdjustHeap(i, m); {Vun danh sách cạnh thành đống}
for i := m - 1 downto 0 do {Rút lần lượt các cạnh khỏi đống, từ cạnh ngắn tới cạnh dài}
begin
tmp := e[1]; e[1] := e[i + 1]; e[i + 1] := tmp;
AdjustHeap(1, i);
r1 := GetRoot(e[i + 1].u); r2 := GetRoot(e[i + 1].v);
if r1 <> r2 then {Cạnh e[i + 1] nối hai cây khác nhau}
begin
e[i + 1].Mark := True; {Kết nạp cạnh đó vào cây}
Inc(Count); {Đếm số cạnh}
if Count = n - 1 then {Nếu đã đủ số thì thành công}
begin
Connected := True;
Exit;
end;
Union(r1, r2); {Hợp nhất hai cây thành một cây}
end;
end;
end;
procedure PrintResult;
var
i, Count, W: Integer;
f: Text;
begin
Assign(f, OutputFile); Rewrite(f);
if not Connected then
WriteLn(f, 'Error: Graph is not connected')
else
begin
WriteLn(f, 'Minimum spanning tree: ');
Count := 0;
W := 0;
for i := 1 to m do {Duyệt danh sách cạnh}
with e[i] do
begin
if Mark then {Lọc ra những cạnh đã kết nạp vào cây khung}
begin
WriteLn(f, '(', u, ', ', v, ') = ', c);
Inc(Count);
W := W + c;
end;
if Count = n - 1 then Break; {Cho tới khi đủ n - 1 cạnh}
end;
WriteLn(f, 'Weight = ', W);
Chuyên đề: Tìm cây khung có trọng số nhỏ nhất 3
4. Trường THPT chuyên Tiền Giang Lớp 11 Tin
end;
Close(f);
end;
begin
LoadGraph;
Init;
Kruskal;
PrintResult;
end.
III – Thuật toán Prim
Thuật toán Kruskal hoạt động chậm trong trường hợp đồ thị dày (có nhiều cạnh). Trong trường hợp đó
người ta thường sử dụng phương pháp lân cận gần nhất của Prim.
a) Ý tưởng:
Nạp dần các đỉnh vào cây khung. Mỗi lần chọn một đỉnh chưa nạp là đỉnh kề và gần với các đỉnh
đã nạp nhất.
Các bước thực hiện cụ thể
1) Nạp đỉnh đầu tiên vào cây khung (nếu đề bài không yêu cầu nhất thiết phải là đỉnh nào thì
chọn tùy ý thường là đỉnh 1).
2) Lần lượt nạp N-1 đỉnh còn lại (tương ứng là N-1 cạnh) vào cây khung bằng cách: mỗi lần
chọn một cạnh có trọng số nhỏ nhất mà một đầu của cạnh đã thuộc cây, đầu kia chưa
thuộc cây.
b) Mô hình thuật thoán Prim:
for (∀v ∈ V) do
begin
d[v] := +∞;
Free[v] := True; {Chưa có đỉnh nào ∈ cây}
end;
d[1] := 0;
for k := 1 to n do
begin
u := arg min(d[v]|v ∈ V and Free[v] = True); {Chọn u là có nhãn tự do nhỏ nhất}
Free[u] := False; {Cố định nhãn đỉnh u ⇔ kết nạp u vào cây}
for (∀v ∈ V: (u, v) ∈ E) do
if d[v] > c[u, v] then
begin
d[v] := c[u, v];
Trace[v] := u;
end;
end;
〈Thông báo cây khung gồm có các cạnh (Trace[v], v) với ∀v ∈ V: v ≠ 1)〉;
c) Chương trình:
{$MODE DELPHI} (*This program uses 32-bit Integer [-231..231 - 1]*)
program Finidng_Minimum_Spanning_Tree_using_Prim_Algorithm;
const
InputFile = 'MINTREE.INP';
OutputFile = 'MINTREE.OUT';
max = 1000;
maxCE = 1000;
maxC = max * maxCE;
var
c: array[1..max, 1..max] of Integer;
d: array[1..max] of Integer;
Free: array[1..max] of Boolean;
Trace: array[1..max] of Integer; {Vết, Trace[v] là đỉnh cha của v trong cây khung nhỏ nhất}
Chuyên đề: Tìm cây khung có trọng số nhỏ nhất 4
5. Trường THPT chuyên Tiền Giang Lớp 11 Tin
n, m: Integer;
Connected: Boolean;
procedure LoadGraph; {Nhập đồ thị}
var
i, u, v: Integer;
f: Text;
begin
Assign(f, InputFile); Reset(f);
ReadLn(f, n, m);
for u := 1 to n do
for v := 1 to n do
if u = v then c[u, v] := 0 else c[u, v] := maxC; {Khởi tạo ma trận trọng số}
for i := 1 to m do
begin
ReadLn(f, u, v, c[u, v]);
c[v, u] := c[u, v];
end;
Close(f);
end;
procedure Init;
var
v: Integer;
begin
d[1] := 0; {Đỉnh 1 có nhãn khoảng cách là 0}
for v := 2 to n do d[v] := maxC; {Các đỉnh khác có nhãn khoảng cách +∞}
FillChar(Free, SizeOf(Free), True); {Cây T ban đầu là rỗng}
end;
procedure Prim;
var
k, i, u, v, min: Integer;
begin
Connected := True;
for k := 1 to n do
begin
u := 0; min := maxC; {Chọn đỉnh u chưa bị kết nạp có d[u] nhỏ nhất}
for i := 1 to n do
if Free[i] and (d[i] < min) then
begin
min := d[i];
u := i;
end;
if u = 0 then {Nếu không chọn được u nào có d[u] < +∞ thì đồ thị không liên thông}
begin
Connected := False;
Break;
end;
Free[u] := False; {Nếu chọn được thì đánh dấu u đã bị kết nạp, lặp lần 1 thì dĩ nhiên u = 1 bởi
d[1] = 0}
for v := 1 to n do
if Free[v] and (d[v] > c[u, v]) then {Tính lại các nhãn khoảng cách d[v] (v chưa kết nạp)}
begin
d[v] := c[u, v]; {Tối ưu nhãn d[v] theo công thức}
Trace[v] := u; {Lưu vết, đỉnh nối với v cho khoảng cách ngắn nhất là u}
end;
end;
end;
procedure PrintResult;
var
v, W: Integer;
f: Text;
begin
Assign(f, OutputFile); Rewrite(f);
if not Connected then {Nếu đồ thị không liên thông thì thất bại}
Chuyên đề: Tìm cây khung có trọng số nhỏ nhất 5
6. Trường THPT chuyên Tiền Giang Lớp 11 Tin
WriteLn(f, 'Error: Graph is not connected')
else
begin
WriteLn(f, 'Minimum spanning tree: ');
W := 0;
for v := 2 to n do {Cây khung nhỏ nhất gồm những cạnh (v, Trace[v])}
begin
WriteLn(f, '(', Trace[v], ', ', v, ') = ', c[Trace[v], v]);
W := W + c[Trace[v], v];
end;
WriteLn(f, 'Weight = ', W);
end;
Close(f);
end;
begin
LoadGraph;
Init;
Prim;
PrintResult;
end.
Xét về độ phức tạp tính toán, thuật toán Prim có độ phức tạp là O(n2). Tương tự thuật toán Dijkstra, nếu kết
hợp thuật toán Prim với cấu trúc Heap sẽ được một thuật toán với độ phức tạp O((m+n)lgn). Tuy nhiên nếu
phải làm việc với đồ thị thưa, người ta thường sử dụng thuật toán Kruskal để tìm cây khung chứ không dùng
thuật toán Prim với cấu trúc Heap.
Đỗ Cao Thượng Dương
http://thtvn.tk
Chuyên đề: Tìm cây khung có trọng số nhỏ nhất 6