Tải bản đầy đủ (.doc) (24 trang)

giới thiệu các giải thuật đồ thị cơ bản trong toán rời rạc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (432.11 KB, 24 trang )

Đường đi Euler
Hỏi: Các hình này có vẽ được một nét không?
Trả lời: Được! Nhưng điểm cuối không trùng điểm xuất phát
Trong lý thuyết đồ thị, một đường đi trong đồ thị G=(X,E) được gọi là đường đi Euler nếu nó đi
qua tất cả các cạnh của đồ thị, mỗi cạnh đúng một lần. Đường đi Euler có đỉnh cuối cùng trùng với
đỉnh xuất phát gọi là chu trình Euler. Khái niệm chu trình Euler xuất phát từ bài toán bảy cây cầu
do Euler giải quyết vào khoảng năm 1737. Đường đi Euler có thể tìm thấy trong các bài toán vui vẽ
một nét (vẽ một hình nào đó mà không nhấc bút khỏi mặt giấy, không tô lại cạnh nào hai lần).
Carl Hierholzer là người đầu tiên mô tả hoàn chỉnh đồ thị Euler vào năm 1873, bằng cách chứng
minh rằng đồ thi Euler là đồ thị liên thông không có đỉnh bậc lẻ.
Định nghĩa về chu trình và đường đi Euler
1. Đường đi Euler (tiếng Anh: Eulerian path, Eulerian trail hoặc Euler walk) trong đồ thị vô
hướng là đường đi của đồ thị đi qua mỗi cạnh của đồ thị đúng một lần.
2. Chu trình Euler (tiếng Anh: Eulerian cycle, Eulerian circuit hoặc Euler tour) trong đồ thị vô
hướng) là một chu trình đi qua mỗi cạnh của đồ thị đúng một lần.
3. Đồ thị gọi là đồ thị Euler khi nó chứa chu trình Euler, và được gọi là nửa Euler khi nó chứa
đường đi Euler.
4. Đối với các đồ thị có hướng, các thuật ngữ đường đi và chu trình được thay bằng đường đi
có hướng và chu trình có hướng.
• Ghi chú: Một đồ thị là Euler thì sẽ là nửa Euler; điều ngược lại không đúng.
Định lý Euler về chu trình và đường đi Euler
Đồ thị Bảy cây cầu Königsberg có 4 đỉnh bậc lẻ nên không là đồ thị Euler
1. Đồ thị vô hướng liên thông G=(X, E) có chu trình Euler khi và chỉ khi G không có đỉnh bậc
lẻ.
1
2. Đồ thị vô hướng liên thông G=(X, E) có đường đi Euler khi và chỉ khi G có không quá hai
đỉnh bậc lẻ. Nếu G có hai đỉnh bậc lẻ thì đường đi Euler có hai đầu đường đi nằm ở hai đỉnh
bậc lẻ.
Các tính chất khác
1. Một đồ thị vô hướng là đồ thị Euler nếu nó liên thông và có thể phân tích thành các chu trình
có các cạnh rời nhau.


2. Nếu đồ thị vô hướng G là Euler thì đồ thị đường L(G) cũng là Euler.
3. Đồ thị có hướng là Euler nếu nó liên thông và mọi đỉnh của nó có bậc vào bằng bậc ra.
4. Đồ thị có hướng là Euler nếu nó liên thông và có thể phân tích thành các chu trình có hướng
với các cung rời nhau.
Giải thuật
Bỏ cây cầu nối B với D, xây cây cầu nối A với C, đồ thị không có đỉnh bậc lẻ nên là đồ thị Euler
Giả sử G=(V,E) là đồ thị vô hướng, liên thông, tất cả các đỉnh đều có bâc chẵn hơn nữa G là hữu
hạn. Khi đó, tất cả các đỉnh đều có bậc lớn hơn 1.
Giải thuật 2: Không đi qua cầu
• Một cạnh của đồ thị G được gọi là cầu, nếu khi xóa cạnh đó khỏi đồ thị thì làm tăng số thành
phần liên thông của G.
Giải thuật Gọi chu trình cần tìm là C
1. Khởi tạo: Chọn một đỉnh bất kỳ cho vào C.
2. Nếu G không còn cạnh nào thì dừng.
3. Bổ sung: Chọn một cạnh nối đỉnh vừa chọn với một đỉnh kề với nó theo nguyên tắc: chỉ chọn
cạnh cầu nếu không còn cạnh nào khác để chọn. Bổ sung cạnh vừa chọn và đỉnh cuối của nó
vào C, xóa cạnh ấy khỏi G. Quay về bước 2.
Xem thêm
Bài toán bảy cây cầu Euler
2
Bản đồ Königsberg thời Euler, mô tả vị trí thực của bay cây cầu và sông Pregel.
Bài toán bảy cây cầu Euler, còn gọi là Bảy cầu ở Königsberg nảy sinh từ nơi chốn cụ thể. Thành
phố Königsberg, Đức (nay là Kaliningrad, Nga) nằm trên sông Pregel, bao gồm hai hòn đảo lớn nối
với nhau và với đất liền bởi bảy cây cầu. Câu hỏi đặt ra là có thể đi theo một tuyến đường mà đi qua
mỗi cây cầu đúng một lần rồi quay lại điểm xuất phát hay không. Năm 1736, Leonhard Euler đã
chứng minh rằng điều đó là không thể được.
Người ta kể rằng, khoảng năm 1750, vào các ngày Chủ nhật, những người dân giàu có và học thức
của thành phố đã đi dạo quanh để tìm cách giải bài này, nhưng đây có lẽ chỉ là một truyền thuyết.
Lời giải của Euler
Để chứng minh kết quả, Euler đã phát biểu bài toán bằng các thuật ngữ của lý thuyết đồ thị. Ông loại

bỏ tất cả các chi tiết ngoại trừ các vùng đất và các cây cầu, sau đó thay thế mỗi vùng đất bằng một
điểm, gọi là đỉnh hoặc nút, và thay mỗi cây cầu bằng một đoạn nối, gọi là cạnh hoặc liên kết. Cấu
trúc toán học thu được được gọi là một đồ thị.
→ →
Hình thù của đồ thị có thể bị bóp méo theo đủ kiểu nhưng không làm đồ thị bị thay đổi, miễn là các
liên kết giữa các nút giữ nguyên. Việc một liên kết thẳng hay cong, một nút ở bên phải hay bên trái
một nút khác là không quan trọng.
Euler nhận ra rằng bài toán có thể được giải bằng cách sử dụng bậc của các nút. Bậc của một nút là
số cạnh nối với nó; trong đồ thị các cây cầu Königsberg, ba nút có bậc bằng 3 và một nút có bậc 5.
Euler đã chứng minh rằng một chu trình có dạng như mong muốn chỉ tồn tại khi và chỉ khi không có
nút bậc lẻ. Một đường đi như vậy được gọi là một chu trình Eul er . Do đồ thị các cây cầu Königsberg
có bốn nút bậc lẻ, nên nó không thể có chu trình Euler.
3
Có thể sửa đổi bài toán để yêu cầu một đường đi qua tất cả các cây cầu nhưng không cần có điểm
đầu và điểm cuối trùng nhau. Đường đi như vậy được gọi là một đường đi Euler. Một đường đi như
vậy tồn tại khi và chỉ khi đồ thị có đúng hai đỉnh bậc lẻ. (Như vậy điều này cũng không thể đối với
bảy cây cầu ở Königsberg.)
Ý nghĩa của bài toán đối với lịch sử toán học
Trong lịch sử toán học, lời giải của Euler cho bài toán bảy cây cầu ở Königsberg được coi là định lý
đầu tiên của lý thuyết đồ thị, ngành nghiên cứu mà nay được coi là một nhánh của toán học tổ hợp
(combinatorics), tuy các bài toán tổ hợp đã được quan tâm đến từ sớm hơn rất nhiều.
Ngoài ra, nhận xét của Euler rằng thông tin quan trọng là số cây cầu và danh sách các vùng đất ở đầu
cầu (chứ không phải vị trí chính xác của chúng) đã là dấu hiệu cho sự phát triển của ngành tôpô học.
Sự khác biệt giữa sơ đồ thực và sơ đồ đồ thị là một ví dụ tốt rằng tôpô học không quan tâm đến hình
thù cứng nhắc của các đối tượng.
4
Đường đi Hamilton
Trong toán học, ngành lý thuyết đồ thị, một đường đi Hamilton là một đường đi trong đồ thị vô
hướng đi qua tất cả các đỉnh của đồ thị, mỗi đỉnh đúng một lần. Một Chu trình Hamilton là một
đường đi Hamilton sau đi qua tất cả các đỉnh của đồ thị thì trở về đỉnh xuất phát.

Một đồ thị có chu trình Hamilton được gọi là đồ thị Hamilton, đồ thị có đường đi Hamilton được gọi
là đồ thị nửa Hamilton.
Bài toán tìm đường đi và chu trình như vậy được gọi là bài toán Hamilton. Bài toán Hamilton là NP
đầy đủ.
Tên gọi đường đi và chu trình Hamilton là gọi theo tên của William Rowan Hamilton .
Các ví dụ
Đường đi của quân mã trên bàn cờ 3×4 là đường Hamilton.
Các đồ thị Hamilton
• Một đồ thị đầy đủ có nhiều hơn hai đỉnh là đồ thị Hamilton.
• Mọi đồ thị vòng là Hamilton.
• Đồ thị khối ba chiều là đồ thị Hamilton
Định lý Bondy-Chvátal
5
Cho đồ thị G có n đỉnh, bao đóng cl(G) được tạo ra từ G bằng cách bổ sung cho mỗi cặp đỉnh không
kề nhau u và v với degree(v) + degree(u) ≥ n một cạnh mới uv.
Định lý Bondy-Chvátal (1972)
Một đồ thị là Hamilton nếu và chỉ nếu bao đóng của nó là Hamilton.
Vì đồ thi đầy đủ là Hamilton, nên tất cả các đồ thị mà bao đóng là đầy đủ là Hamilton.
Dirac (1952)
Một đơn đò thị n đỉnh (n > 2) là Hamilton nếu mọi đỉnh của nó có bậc không nhỏ hơn n/2.
Ore (1960)
Một đồ thị có n đỉnh (n > 2) là Hamilton nếu tổng các bậc của hai đỉnh không kề nhau đều
bằng n hoặc lớn hơn.
6
Cây bao trùm nhỏ nhất

Cây bao trùm nhỏ nhất của đồ thị mặt phẳng.
Tìm cây bao trùm nhỏ nhất (tiếng Anh: minimum spanning tree) là bài toán tối ưu có nhiều ứng
dụng trong thực tế. Nó có thể là bài toán tìm hệ thống liên thông với chi phí nhỏ nhất, hoặc ngược
lại, vói lợi nhuân lớn nhất. Hai thuật toán tìm cây bao trùm nhỏ nhất và lớn nhất thường được nhắc

đến là thuật toán Prim và thuật toán Krusskal.
• Xem thêm Thuật toán tìm cây bao trùm
Bài toán
Cho G=(X,E) là một đồ thị liên thông. Ngoài ra, một hàm trọng số W(e) nhận các giá trị thực, xác
định trên tập các cạnh E của G. Cả hai thuật toán Prim và Kruskal đều dựa trên tư tưởng của các giải
thuật tham lam: Ở mỗi bước của thuật toán ta chọn và bổ sung vào cây cạnh có trọng số nhỏ nhất có
thể.
Giải thuật Prim
Giải thuật Prim dựa trên cấu trúc của giải thuật tìm cây bao trùm theo chiều rộng hoặc chiều sâu, chỉ
thay đổi về tiêu chuẩn chọn đỉnh sẽ bổ sung vào cây ở từng bước.
// Vài nét R. C. Prim
Robert Clay Prim (sinh 1921 tại Sweetwater, Texas) là một nhà toán học và khoa học máy tính Mỹ.
Năm 1941 ông đã lấy bằng cử nhân ở khoa kỹ thuật điện đại học Princeton. Sau này năm 1949, ông
nhận bằng Ph.D. về toán học cũng tại đây. Giải thuật mang tên Prim được tìm ra từ năm 1930 bởi
nhà toán học Vojtěch Jarník và do Prim hoàn thiện vào năm 1957.
Mô tả
7
Gọi T là cây bao trùm sẽ xây dựng
1. Chọn một đỉnh s bất kỳ của G cho vào cây T. Khi đó T là một cây chỉ có một đỉnh và chưa có
cạnh nào.
2. Nếu T đã gồm tất cả các đỉnh của G thì T là cây bao trùm cần tìm. Kết thúc.
3. Nếu G còn có các đỉnh không thuộc T, vì G liên thông nên có các cạnh nối một đỉnh trong T
với một đỉnh ngoài T, chọn một cạnh có trọng số nhỏ nhất trong số đó cho vào T.
4. Quay lại 2.
Giả mã
Giải thuật trình bày Prim ở trên dễ dàng thực hiện khi ta quan sát một đồ thị được biểu diễn trên mặt
phẳng với một số ít đỉnh. Tuy nhiên để cài đặt thành một chương trình chạy trên máy tính cũng cần
phân tích thêm một số điểm.
Liệu có thể sử dụng một hàng đợi (queue) giống như trong giải thuật tìm cây bao trùm theo chiều sâu
không? Có. Nhưng điều khác biệt là ta sẽ sử dụng một hàng đợi có ưu tiên, tiêu chuẩn ưu tiên ở đây

là cạnh nối đỉnh sẽ được bổ sung vào T là nhỏ nhất. Một cấu trúc thuận lợi cho hàng đợi có ưu tiên là
cấu trúc đống (heap) như đã sử dụng trong giải thuật sắp xếp vun đống hoặc giải thuật xây dựng mã
Huffman. Giả sử G=(X,E) là đồ thị liên thông, Ajd(u), là danh sách đỉnh kề của đỉnh u. L(e)
là hàm trọng số xác đinh với moi cạnh . Trong đoạn mã sau ta dùng hàm COLOR(u) để tô
màu các đỉnh của G bằng các màu WHITE, GRAY, BLACK để chỉ các trạng thái chưa xét, đang xét
và đã xét xong của mỗi đỉnh, hàm PARENTS(u) chỉ đỉnh cha của đỉnh u trong cây, hàm
DISTAN(u,T) chỉ trọng số nhỏ nhất của các cạnh nối từ đỉnh u ngoài T đến các đỉnh trong T. Ta
cũng sử dụng một cấu trúc HEAP làm hàng đợi ưu tiên chứa các đỉnh đang chờ xét.
Ở bước khởi tạo, một đỉnh r bất kỳ được đưa vao Heap. Số các phần tử của đống là n=1. Cho đến khi
hàng đợi Heap còn khác rỗng ta thực hiện các bước sau: lấy đỉnh u nằm ở gốc Heap ra để đưa vào
cây. Đối với đỉnh u mới được bổ sung vào cây ta duyệt qua tất cả các đỉnh v thuộc danh sách kề với
nó. Nếu đỉnh v chưa thuôch T và không nằm trong đống ta chèn v vào đống, nếu đỉnh v đã nằm
trong đống đợi ta tính lại khoảng cách từ v của nó với tập các đỉnh đã nằm trong T, và vun lại đống.
Sau khi tất cả các đỉnh ) đã duyệt qua bằng cách ấy thì thì đỉnh u đã được duyệt xong
và được đưa vào cây.
Procedure Prim (G, r, W(e)) {
Var list COLOR(u), PARENTS(u), DISTAN(u),Heap H

For each u of E {
COLOR(u)= WHITE
PARENTS(u)=Null
DISTAN(u)= M
}
H(1)=r
n=1
DISTAN(r)=0
While n> 0 do
u=H(1)
n=n-1
Color(u)=BLACK

For each v of Ajd(u) {
if (Color(v)=WHITE)
or ((Color(v)=GRAY) and (DISTAN(v)<W(u,v)
8
then
{
COLOR(v)=GRAY
PARENTS(v)=u
DISTAN(v)=W(u,v)
if Color(v)=WHITE then InsertHeap(H,v)
else UpHeap(H,HeapIndex(v))
}
}
}

Một chương trình đầy đủ bằng ngôn ngữ Pascal
program Prim_Algorithm;
uses dos,crt;
const max=150;
type
filename=string[12];
var
TrongSo: array[1 max,1 max] of integer;
DinhKe: array[1 max] of integer;
CanhKe: array[1 max] of integer;{}
n, DodaiCayKhung: integer;
i,j: integer;
fname: filename;
ch: char;
h,m,s, hund:word;

procedure PrintMatrix;
Begin
(*In ma tran ra *)
Writeln(' ');
Writeln('Ma tran trong so, Khong co canh noi thi trong so = MaxInt');
for i:=1 to n do
Begin
For j:=1 to n do
if TrongSo[i,j]=Maxint then write(' 0',' ')
else write(TrongSo[i,j]:5,' ');
Writeln;
End;
Writeln(' ');
End;
procedure
ReadInputFile(fname: filename);
var
i,j: integer;
f: text;
begin
assign(f,fname);
reset(f);
readln(f,n);
for i:=1 to n do
begin
for j:=1 to n do
begin
read(f,TrongSo[i,j]);
if TrongSo[i,j]=0 then TrongSo[i,j]:=MaxInt;
end;

readln(f);
9
end;
close(f);
PrintMatrix;
end;
procedure Prim;
var
v,i,k: integer;
min: integer;
begin
for v:=2 to n do
begin
DinhKe[v]:=1;
CanhKe[v]:=TrongSo[1,v];
end;
for i:=2 to n do
begin
min:=MaxInt;
for k:=2 to n do
if (CanhKe[k]>=0) and (CanhKe[k]<min) then
begin
min:=CanhKe[k]; (
v:=k;
end;
CanhKe[v]:=-1;
for k:=2 to n do
if (TrongSo[v,k]<CanhKe[k]) and (TrongSo[v,k]>0) then
begin
CanhKe[k]:=TrongSo[v,k];

DinhKe[k]:=v;
end;
end;
end;
procedure
WriteOutputFile;
var
i: integer;
f: text;
begin
assign(f,'prim.out');
rewrite(f);
Writeln(f,' ');
Writeln(f,'Ma tran trong so, Khong co canh noi thi trong so = MaxInt');
for i:=1 to n do
Begin
For j:=1 to n do
if TrongSo[i,j]=Maxint then write(f,' 0',' ')
else write(f,TrongSo[i,j]:5,' ');
Writeln(f,);
End;
Writeln(f,' ');
DodaiCayKhung:=0;
writeln(f,'Cay khung nho nhat gom cac canh:');
for i:=2 to n do
begin
write(f,'(',i,',',DinhKe[i],') ');
DodaiCayKhung:=DodaiCayKhung+TrongSo[i,DinhKe[i]];
end;
writeln(f);

writeln(f,'Length: ',DodaiCayKhung);
close(f);
10
end;
begin
repeat
clrscr;
writeln('THUAT TOAN PRIM TIM CAY KHUNG NHO NHAT');
writeln(' DUNG MA TRAN KE');
writeln(' ');
writeln(' Hay bam phim chuc nang:');
Writeln;
writeln(' K(eyboard). Chay chuong trinh - Du lieu nhap tu ban phim.');
Writeln;
writeln(' F(ile). Chay chuong trinh - Du lieu lay tu File.');
Writeln;
writeln(' R(andom). Chay chuong trinh - Du lieu Random.');
Writeln;
writeln(' Q(uit). Thoat khoi chuong trinh.');
Writeln;
ch:=ReadKey;
case UpCase(ch) of
'F':
begin
write(' Hay nhap ten tep du lieu:'); readln(fname);
ReadInputFile(fname);
gettime(h,m,s,hund);
Writeln('Bat dau chay: ',h,':',m,':',s,':',hund);
Prim;
gettime(h,m,s,hund);

Writeln('Ket thuc chay: ',h,':',m,':',s,':',hund);
WriteOutputFile;
write('Cac canh cua cay khung be nhat:');
for i:=2 to n do write('(',i,',',DinhKe[i],')');
writeln;
writeln('Gia cua cay khung : ',DodaiCayKhung);
writeln('***************************************');
writeln('Nhan phim Enter de tiep tuc.');
readln;
end;
'R':
begin
write('Hay nhap so dinh cua do thi:'); readln(n);
for i:=1 to n do TrongSo[i,i]:=0;
for i:=1 to n-1 do
For j:=i+1 to n do TrongSo[i,j]:=random(100);
for i:=2 to n do
For j:=1 to i-1 do TrongSo[i,j]:=TrongSo[j,i];
PrintMatrix;
for i:=1 to n do
for j:=1 to n do
if TrongSo[i,j]=0 then TrongSo[i,j]:=MaxInt;
gettime(h,m,s,hund);
Writeln('Bat dau chay: ',h,':',m,':',s,':',hund);
Prim;
gettime(h,m,s,hund);
Writeln('Ket thuc chay: ',h,':',m,':',s,':',hund);
write('Cac canh cua cay khung be nhat:');
for i:=2 to n do write('(',i,',',DinhKe[i],')');
WriteOutputFile;

Writeln;
writeln('Gia cua cay khung : ',DodaiCayKhung);
writeln;
11
writeln('***************************************');
writeln('Nhan phim Enter de tiep tuc.');
readln;
end;
'K':
begin
write('Hay nhap so dinh cua do thi:'); readln(n);
for i:=1 to n do TrongSo[i,i]:=0;
for i:=1 to n-1 do
For j:=i+1 to n do
Begin
Write('a[',i,j,']=');
readln(TrongSo[i,j]); (* Nua tren *)
End;
for i:=2 to n do
For j:=1 to i-1 do TrongSo[i,j]:=TrongSo[j,i];
PrintMatrix;
for i:=1 to n do
for j:=1 to n do
if TrongSo[i,j]=0 then TrongSo[i,j]:=MaxInt;
gettime(h,m,s,hund);
Writeln('Bat dau chay: ',h,':',m,':',s,':',hund);
Prim;
gettime(h,m,s,hund);
Writeln('Ket thuc chay: ',h,':',m,':',s,':',hund);
write('Cac canh cua cay khung be nhat:');

for i:=2 to n do write('(',i,',',DinhKe[i],')');
WriteOutputFile;
writeln;
writeln('Gia cua cay khung : ',DodaiCayKhung);
writeln('***************************************');
writeln('Nhan phim Enter de tiep tuc.');
readln;
end;
end;
until
UpCase(ch)='Q';
end.
Các thao tác trên Heap
• Trong đoạn mã trên có sử dụng hàm InsertHeap(H,v) để chèn đỉnh v vào đống H, hàm
UpHeap(H,v) để sắp xếp lại đống H (vun lại đống) khi thay giá trị DISTAN(v) bằng giá trị
nhỏ hơn.
• Trong các thủ tục về Heap, ta tổ chức Heap như một mảng H(1 n) trong đó mỗi phần tử
trong nó là các đỉnh của G được sắp xếp sao cho DISTAN(H(i)) ≤ DISTAN(H(2*i)) (nếu 2*i
≤n) và DISTAN(H(i)) ≤ DISTAN(H(2*i+1)) (nếu 2*i+1 ≤ n).
• Vì Heap chứa các đỉnh nên ta thêm một mảng HeapIndex(v) để xác định vị trí của đỉnh v
trong Heap.
• Thao tác InsertHeap thêm một phần tử vào cuối Heap và điều chỉnh lại vị trí các phần tử sao
cho nó vẫn là Heap nhờ thao tác UpHeap(H,n).
• Thao tác UpHeap(k) điều chỉnh lại vị trí của phần tử v nằm tại vị trí thứ k của Heap khi giá
trị DISTAN(v) thay bằng giá trị nhỏ hơn. Rõ ràng khi đó chỉ cần so sánh DISTAN của v với
DISTAN của đỉnh cha của trong Heap.
Procedure InsertHeap(H,v){
12
n:=n+1
Heap(n):=v

IndexHeap(v)=n
UpHeap(H,n)
}
Procedure UpHeap (H,k){
i:= Lshift(k,1) /*Chia đôi lấy phần nguyên*/
While (k>0) and DISTAN(H(k))<DISTAN(H(i)) {
Swap(H(k),H(i)) /*Đổi chỗ H(k) H(i)*/
k:=i
i:=LShift(k,1) /*Chia đôi lấy phần nguyên*/
}
}
13
Giải thuật Kruskal
Giải thuật Kruskal không dựa trên tư tưởng của các thuật toán tìm kiếm theo chiều rộng hoặc chiều
sâu. Trong các thuật toán này, tại từng bước của quá trình xây dựng T luôn là một cây, chỉ có điều
kiện về số đỉnh của T phải đến bước cuối cùng mới thỏa mãn. Còn trong thuật toán Kruskal, trong
quá trình xây dựng T có thể chưa là cây, nó chỉ thỏa mãn điều kiện không có chu trình.
Mô tả
Giả sử G liên thông có n đỉnh. Gọi T là cây bao trùm sẽ xây dựng.
1. Khởi tạo: T lúc đầu là một đồ thị rỗng.
2. Nếu T đã gồm đúng n-1 cạnh của G thì T là cây bao trùm cần tìm. Kết thúc.
3. Nếu T còn chưa đủ n-1 cạnh, thì vì G liên thông, nên G có không ít hơn n-1 cạnh, do đó còn
các cạnh của G chưa thuộc T. Trong các cạnh của G chưa thuộc T có các cạnh không tạo ra
chu trình với các cạnh đã có trong T, Chọn cạnh v có trọng số nhỏ nhất trong các cạnh ấy bổ
sung (cùng với các đỉnh chưa thuộc T của nó) vào T.
4. Quay lại 2.
Một vài tác giả có cách trình bày khác của giải thuật Kruskal, tuy bản chất quá trình là giống nhau
1. Khởi tạo: T lúc đầu gồm tất cả các đỉnh của G và chưa có cạnh nào,như vây T lúc đầu là một
rừng n cây,mỗi cây gồm đúng một đỉnh.
2. Nếu T chỉ gồm một cây thì dừng

3. Nếu T gồm nhiều hơn một cây, chọn cạnh nhỏ nhất của G có hai đầu mút thuộc hai cây khác
nhau bổ sung vào T, khi đó hai cây được hợp thành một cây.
4. Quay lại 2.
Giả mã
Phân tích
• Khi lập trình cài đặt giải thuật Kruskal có hai điểm mấu chốt cần chú ý:
1. Trong mỗi lần lặp ta chọn cạnh có trọng số nhỏ nhất đưa vào T.
2. Cạnh được chọn đưa vào T phải không tạo thành chu trình với các cạnh đã có trong T.
• Vấn đề thứ nhất được giải quyết gần giống như trong giải thuật Prim là tạo một hàng đợi có
ưu tiên trên danh sách các cạnh. Tuy nhiên có thể ngay từ đầu sắp xếp các cạnh theo thứ tự
tăng dần của trọng số.
Để giải quyết vần đề thứ hai, ta quay lại chú ý rằng, khác với giải thuật Prim, tại mỗi bước của giải
thuật Kruskal, tập các đỉnh và các cạnh đã được đưa vào T chưa là cây mà chỉ thỏa mã tính không
chu trình. Như vậy tại mỗi bước T là một rừng, T = .
• Bây giờ xét một cạnh e = (u,v) của G chưa nằm trong T có 3 khả năng xảy ra:
14
1. Cả u,v chưa thuộc T. Khi đó nếu bổ sung e vào T thì không có chu trình, nhưng chính cạnh e
tạo thành một cây con mới.
2. Một đỉnh chẳng hạn u thuộc T, còn đỉnh kia v không thuộc T. Việc bổ sung cạnh e và đỉnh v
vào T (vào cây con chứa đỉnh u) không tạo ra chu trình.
3. Cả u,v đều nằm trong T. Khi đó
1. Nếu u,v nằm trong cùng một cây con T
k
ta không thể bổ sung canh e vào T.
2. Nếu u,v nằm trong hai cây con khác nhau thì có thể bổ sung cạnh e vào T (hai đỉnh u,
v đã nằm trong T không cần bổ sung, sau khi bổ sung hai cây con sẽ hợp lại thành
một cây.
Tổ chức dữ liệu
• Giả sử G=(V,E)là đồ thị vô hướng n đỉnh, cho bằng danh sách kề Ajd(u), . Các đỉnh
của G được đánh số từ 1 đến n, nghiã là V=V[1 n];Ta cũng kí hiệu Index(u) là chỉ số của

đỉnh u trong mảng V.
Hàm trọng số W(u,v) xác định trên các cạnh . Với mỗi cạnh e=(u,v) ký hiệu e.x u,
e.y=v là hai đỉnh liên thuộc với cạnh e.
• Các biến sau được đưa vào
o Hàng đợi Q của các cạnh xếp theo thứ tự trọng số từ nhỏ đến lớn.
o Với mỗi cây con, hàm PARENTS(u) xác định trên V, biểu diễn chỉ số của đỉnh cha
của đỉnh u trong một cây. Riêng đỉnh gốc u của mỗi cây hàm PARENTS(u)lưu trữ số
đỉnh trong cây với dấu âm.

Procedure Kruskal (G)
T = V;
Q =Sort(E)
/* Tạo n cây, mỗi cây gồm một đỉnh*/
For each u of X do Parent(u):=-1;
m = |E|
For j:=1 to m do {
u:=NodeStart(Q(j)); V:=NodeEnd(Q(j))
If Find_tree(u)<>Find_tree(V) then
T:=T U Q[j];
Union(u,v;)

15
Thuật toán Dijkstra
Thuật toán Dijkstra, mang tên của nhà khoa học máy tính người Hà Lan Edsger Dijkstra, là một
thuật toán giải quyết bài toán đường đi ngắn nhất nguồn đơn trong một đồ thị có hướng không có
cạnh mang trọng số âm.
Bài toán
Cho một đồ thị có hướng G=(V,E), một hàm trọng số w: E → [0, ∞) và một đỉnh nguồn s. Cần tính
toán được đường đi ngắn nhất từ đỉnh nguồn s đến mỗi đỉnh của đồ thị.
Ví dụ: Chúng ta dùng các đỉnh của đồ thị để mô hình các thành phố và các cạnh để mô hình các

đường nối giữa chúng. Khi đó trọng số các cạnh có thể xem như độ dài của các con đường (và do đó
là không âm). Chúng ta cần vận chuyển từ thành phố s đến thành phố t. Thuật toán Dijkstra sẽ giúp
chỉ ra đường đi ngắn nhất chúng ta có thể đi.
Trọng số không âm của các cạnh của đồ thị mang tính tổng quát hơn khoảng cách hình học giữa hai
đỉnh đầu mút của chúng. Ví dụ, với 3 đỉnh A, B, C đường đi A-B-C có thể ngắn hơn so với đường đi
trực tiếp A-C.
Thuật toán
Thuật toán Dijkstra có thể mô tả như sau:
Ta quản lý một tập hợp động S. Ban đầu S={s}.
Với mỗi đỉnh v, chúng ta quản lý một nhãn d[v] là độ dài bé nhất trong các đường đi từ nguồn s đến
một đỉnh u nào đó thuộc S, rồi đi theo cạnh nối u-v.
Chứng minh
Ý tưởng của chứng minh như sau.
Chúng ta sẽ chỉ ra, khi một đỉnh v được bổ sung vào tập S, thì d[v] là giá trị của đường đi ngắn nhất
từ nguồn s đến v.
Theo định nghĩa nhãn d, d[v] là giá trị của đường đi ngắn nhất trong các đường đi từ nguồn s, qua
các đỉnh trong S, rồi theo một cạnh nối trực tiếp u-v đến v.
Giả sử tồn tại một đường đi từ s đến v có giá trị bé hơn d[v]. Như vậy trong đường đi, tồn tại đỉnh
giữa s và v không thuộc S. Chọn w là đỉnh đầu tiên như vậy.
Mã giả
Phân tích
16
Với giải thuật đã mô tả ta dễ dàng thực hiện trực tiếp trên các đồ thị kích thước nhỏ,để có thể mã hóa
và cài đặt hệ quả cần đưa thêm các cấu trúc dữ liệu để sử dụng trong giải thuật.
Dữ liệu
• Hàm d(u) dùng để lưu trữ độ dài đường đi ngắn nhất từ đỉnh nguồn s đến đỉnh u. Rõ ràng
d(s)= 0. Ký hiệu X

(u) là tập tất cả các đỉnh có cạnh đi tới đỉnh u. Nếu với mọi
đã xác định được d(v) thì:

• Để tính được giá trị nhỏ nhất này, như thông thường khi khởi tạo ta phải gán cho d(v)= ,
sau đó gặp giá trị nhỏ hơn thì thay thế lại.
• Những đỉnh đã tính được d(v)hữu hạn được cho vào một hàng đợi có ưu tiên. Hàng đợi này
luôn được bổ sung và sắp xếp lại nên một cấu trúc hợp lý là cấu trúc đống nhị phân (heap).
• Để theo dõi trạng thái của các đỉnh trong quá trình xét, ta dùng hàm COLOR(u) xác định với
mọi . Lúc đầu các đỉnh được tô màu trắng (WHITE), khi cho vào hàng đợi nó được tô
màu xám (GRAY), khi đã tính xong khoảng cách nó được tô màu đen(BLACK).
• Nếu cần ghi lại đường đi ta sẽ phải dùng một hàm con trỏ PRE(u) để chỉ đỉnh đứng ngay
trước đỉnh u trên đường đi ngắn nhất từ s tới u.

Procedure Dijkstra {
For each v of V do {
d(v)=M
COLOR(v)=WHITE
d(s)=0

InsertHeap(Q,s)
k=1
While Q khác rỗng do {
u=Head(Q)
Push(Q,u)
k=k-1
COLOR(u)=BLACK
For each v of Ajd(u) {
if COLOR(v)=WHITE then {
k=k+1
HeapIndex(v)=k
InsertHeap(Q,v)
COLOR(v)=GRAY
PRE(v)=u

dv=d(u)+w(u,v)
}
if (COLOR(v)=GRAY) and d(v)>d(u)+w(u,v) then{
d(v)=d(u)+w(u,v)
PRE(v)=u
UpHeap(Q,HeapIndex(v))
}
}
}
17
Các thủ tục InsertHeap và UpHeap xem trong thuật toán Prim (Tìm cây bao trùm nhỏ nhất)
Thêm một thủ tục mô phỏng dễ hiểu hơn cho thuật toán này như sau:
Dijkstra(G, s) {
Khởi tạo tập S chỉ chứa đỉnh ban đầu s;
for (mỗi đỉnh v thuộc G) {
D[v] = C(s, v); // C(s, v)=vô cùng nếu s và v không nối với nhau
}
D[s] = 0;
while ( (V-S) != Φ ) {
Chọn đỉnh u thuộc (V-S) sao cho D[u] ngắn nhất;
S = S U {u};
for ( mỗi v thuộc (V-S) ) {
if (D[u] + C(u, v) < D[v]) {
D[v] = D[u] + C(u, v);
}
}
}
}
Ví dụ
Thời gian chạy

Thuật toán Dijkstra bình thường sẽ có độ phức tạp là O( n^2+m ). Tuy nhiên ta có thể sử dụng kết
hợp với cấu trúc heap, khi đó độ phức tạp sẽ là O( (n+m)*log2(n) )
18
Cây bao trùm
Cây bao trùm (tiếng Anh: spanning tree), còn được gọi là cây khung, của đồ thị G là cây con của
đồ thị G, chứa tất cả các đỉnh của G. Nói cách khác, cây bao trùm của một đồ thị G là một đồ thị con
của G, chứa tất cả các đỉnh của G, liên thông và không có chu trình.
Cây bao trùm của đồ thị liên thông G cũng có thể định nghĩa như một đồ thị con không chu trình lớn
nhất, hay một đồ thị con liên thông nhỏ nhất của G.
Mọi đồ thị liên thông đều có cây bao trùm.
Số các cây bao trùm của một đồ thị liên thông
Gọi t(G) là số các cây bao trùm của đồ thị liên thông G. Trong một số trường hợp, số t(G) có thể tính
trực tiếp. Chảng hạn nếu G là một cây, khi đó t(G)=1, còn khi G là một đồ thị vòng C
n
với n đỉnh thì
t(G)=n. Với đồ thị G bất kỳ, số t(G) có tính nhờ Định lý Kirchhoff.
Công thức Cayley là công thức cho số các cây bao trùm của đồ thị đầy đủ K
n
với n đỉnh: t(K
n
) = n
n −
2
.
Nếu G là đồ thị hai phía đầy đủ K
p,q
, thì t(G) = p
q − 1
q
p − 1

, còn nếuG là [[đồ thị khối n-chiều]] Q
n
, thì
. Các công thức này rút ra từ lý thuyết các ma trận.
Nếu G là một đa đồ thị và e là một cạnh của G, thí số t(G) các cây bao trùm của G thỏa mãn quan hệ
t(G)=t(G-e)+t(G/e) (deletion-contraction recurrence), trong đó G-e là đa đò thị suy ra từ G bằng
cách xóa đi cạnh e và G/e là đồ thị rút gọn cạnhe của G, trong đó các cạnh bội xuất hiện từ phpé rút
gọn mày không bị xóa.
Để tìm cây bao trùm, ta có thể áp dụng các thuật toán tìm kiếm theo chiều rộng hoặc tìm kiêm theo
chiều sâu. Giả sử G=(X,E) là đồ thị liên thông. Vì cây bao trùm phải chứa tất cả các đỉnh của đồ thị
nên bất kỳ đỉnh nào cũng phải có mặt trong cây bao trùm. Do đó cả hai thuật toán sau đều lấy điểm
xuất phát từ một đỉnh bất kỳ.
• Xem thêm Tìm cây bao trùm nhỏ nhất
Thuật toán tìm cây bao trùm theo chiều rộng
Tìm kiếm ưu tiên chiều rộng (tiếng Anh: Breadth first search, viết tắt BFS) là thuật toán tìm đồ thị
bắt đầu ở đỉnh gốc và trước tiên tìm kiếm trong các đỉnh kề.
Miêu tả thuật toán
19
Thứ tự viếng thăm các đỉnh trong BFS, gần trước, xa sau
Tư tưởng của thuật toán này là trước tiên tìm trên tất các đỉnh gần với đỉnh xuất phát nhất trong khả
năng có thể. Áp dụng vào tìm cây bao trùm, thuật toán được mô tả như sau
Gọi T là cây con sẽ được xây dưng:
1. Chọn một đỉnh s bất kỳ của đồ thị làm gốc của cây T. Lúc này cây T chỉ có một đỉnh s là gốc
của T., (s có mức 0) và chưa có cạnh nào. Tất cả các đỉnh trong G chưa được xét.
2. Lần lượt xét tất cả các đỉnh trong T có mức thấp nhất chưa xét xong. Mỗi lần xét đỉnh u:
1. Tìm tất cả các cạnh nối đỉnh u với một đỉnh ngoài T.
2. Nếu không có các cạnh như vậy thì đỉnh u đã được xét xong.
3. Nếu có e=(u,v) nối u với v nằm ngoài T thì bổ sung vào T tất cả các cạnh e=(u,v) và
đỉnh v như vậy . Nếu u có mức k thì các đỉnh mới bổ sung có mức k+1. Khi đó đỉnh u
đã được xét xong.

4. Quá trình dừng lại khi tất cả các đỉnh đã nằm trong T đã được xét.
3. T là cây bao trùm cần tìm.
Ví dụ
Với sự mô tả trên đây có thể tìm cây bao trùm dễ dàng trên các đồ thị có số các đỉnh và cạnh tương
đối nhỏ.
Mã giả
Để xây dựng giải thuật trên máy tính cần làm rõ các cấu trúc dữ liệu biểu diễn đồ thị cũng như quá
trình xét các đỉnh và các cạnh. Giả sử đồ thị G cho bởi các danh sách các cạnh kề với từng đỉnh.
Danh sách này thường được ký hiệu là Adj[u] đối với danh scáh các cạnh kề đỉnh u. Để phân biệt
các đỉnh chưa nằm trong T, các đỉnh trong T đã xét xong và chưa xét, ta hình dung một quá trình tô
màu các đỉnh: Đỉnh mới bổ sung vào T thì tô màu xám (GRAY), đỉnh trong T đã xét xong thi tô màu
đen (BLACK), các đỉnh chưa nằm trong T thi tô mầu trắng (WHITE). Ta còn muốn xác định các
cạnh nào nằm trên cây. Xem T như một cây có gốc, trừ gốc, mỗi cạnh trong cây nối một đỉnh với
cha của nó, vì vậy ta dùng một hàm Parent(u) để xác định các cạnh được đưa vào cây T. Vì thế, khi
khởi tạo ta có các biến danh sách COLOR(u) và PARENTS(u). Theo nguyên tắc xét đỉnh gần gốc
nhất, các đỉnh gia nhập cây sớm sẽ được xét trươc. Để "theo dõi" chặt chẽ thứ tự các đỉnh được đưa
vào T ta dùng một cấu trúc hàng đợi Q (Queue).
20
Procedure BFS(G,r) {
Var list COLOR(u),PARENTS(u),Queue Q
/* Khởi tạo */
For each u of E do {
GRAY(u)=WHITE
PARENTS(u)=Null
}
Push(Q,r) /*Đẩy đỉnh đầu tiên vào hàng đợi*/

/*Xét các đỉnh*/
While Q <> rỗng do {
u = Pop(Q);/*Lấy đỉnh đầu hàng đợi Q ra để xét*/

COLOR(s)=GRAY
For each v of Adj(u)
{
if COLOR(v)=WHITE then {
COLOR(v)=GRAY
PARENTS(v)=u
Push(Q,v) /*đẩy đỉnh v vào hàng đợi*/
}
COLOR(u)=BLACK
}
}
Return PARENTS
Thuật toán tìm cây bao trùm theo chiều sâu
Miêu tả thuật toán
Tìm kiếm ưu tiên chiều chiều sâu (tiếng Anh :Depth-first search, viết tắt là DFS) là một thuật toán
tìm kiếm trên đồ thị.
Thứ tự viếng thăm các đỉnh trong DFS,đi càng xa càng tốt, nếu không đi được nữa thì quay lại
Tư tưởng của thuật toán này là trong quá trình tìm các đỉnh của đồ thị để ghép vào cây ta luôn tìm
các tìm các đỉnh càng xa gốc càng tốt. Áp dụng vào tìm cây bao trùm, thuật toán này được mô tả như
sau. Gọi T là cây con sẽ được xây dưng
1. Chọn một đỉnh s bất kỳ của đồ thị làm gốc của cây T. Lúc này cây T chỉ có một đỉnh s là gốc
của T., (s có mức 0) và chưa có cạnh nào. Tất cả các đỉnh trong G chưa được xét.
21
2. Lần lượt xét tất cả các đỉnh trong T có mức cao nhất nhất chưa xét xong. Mỗi lần xét đỉnh u:
Tìm một cạnh nối đỉnh u với một đỉnh ngoài T.
1. Nếu không có các cạnh như vậy thì đỉnh u đã được xét xong. Ta quay về đỉnh đứng
ngay trước đỉnh u.
2. Nếu có cạnh e=(u,v) nối u với v ngoài T thì bổ sung vào T cạnh e và đỉnh v. Nếu u có
mức k thì đỉnh mới bổ sung v có mức k+1. Đỉnh mới bổ sung v chỉnh là đỉnh có mức
cao nhất mới được bổ sung vào T.

3. Quá trình dừng lại khi tất cả các đỉnh nằm trong T đã được xét.
3. T là cây bao trùm cần tìm.
Ví dụ
Mã giả
Trong mã của giải thuật tìm kiếm theo chiều sâu ta cũng đưa vào các biến danh sách CORLOR(u) và
PARENTS(u) trên tập các đỉnh. Sau khi khởi tạo ta dùng cách gọi đệ quy để tìm cây bao trùm của
G(X,E)
Procedure DFS ( G ){
/*Khởi tao*/
For each đỉnh of E do {
COLOR[u] := WHITE
PARENTS(u)=Null
}
/* Tìm kiếm đệ quy*/
For each đỉnh u of E do
if if COLOR[u] = WHITE
then DFS-Visit (u)
Return PARENTS;
}
Procedure DFS-Visit(u) {
COLOR[u]:= GRAY /*Bắt đầu xét u*/
for each v of Adj[u] do {
if COLOR[v] = WHITE
then {
PARENTS[v] := u
DFS-Visit (v)
}
}
COLOR[u]:=BLACK /*Xét xong u*/
}

Giải thuật đệ quy trên đây dễ viết nhưng khó nhìn thấy ý nghĩa thực sự của giải thuật tìm kiếm theo
chiều sâu. Trong nó có chứa một thao tác được gọi là thao tác quay lui: đi xa hết mức nếu có thể,
nếu không thể đi được nữa thì lùi lại xét đỉnh ở bước trước (tức là đỉnh cha của nó).
Giải thuật không đệ quy
Theo nguyên tắc đị xa nhất có thể, giải thuật không đệ quy tìm cây bao trùm theo chiều sâu cần đến
một cấu trúc ngăn xếp S (Stack) để lưu trữ các đỉnh theo thứ tự đưa vào cây. Khi khới tạo ta cho một
đỉnh bất kỳ váo ngăn xếp S.
22
Ở mỗi bước ta lấy đỉnh u nằm cuối Stack ra để xét. Nếu trong danh sách các đỉnh kề với u không còn
đỉnh nào chưa nằm trong cây T (tất cả chúng đã xét xong hoặc nằm trong Stack) thì đỉnh u đã xét
xong, nếu có một đỉnh không nằm trong Stack và chưa xét thì chó nó vào Stack. Quá trình chấm dứt
khi Stack là rỗng.
Procedure DFS(G,r){
Var Stack S, list COLOR(u), PARENTS(u)
/*Khởi tạo: Tất cả các đinh chưa xét */
For each' v of E do {
COLOR(u):=WHITE
PARENTS(u):=NULL
}
/* Cho đỉnh đầu tiên vào Stack*/
COLOR(r):=GRAY
Push(S,r)
While' S khác rỗng do (
u= End(S); /* Xét phần tử mằm sau cùng trong Stack*/
Find:=False ;
For each v of Adj(u) do {
If COLOR(v)=WHITE then {
PARENT(v):=u
COLOR(v):=GRAY
Push(S,v) /*Bổ sung v vào cuối ngăn xếp*/

Breack
find=TRUE
}
if not Find then {
COLOR(u)=BLACK
Pos(S,u) /*Lấy u khỏi Stack */
}
}
}
Trong giải thuật này mỗi lần xét một đỉnh cuối trong ngăn xếp, ta không vội vã đưa nó ra khỏi ngăn
xếp. Nó chỉ được xét xong và đưa khỏi ngăn xếp khi tất cả các đỉnh kề với nó đã được đưa vào cây
(nằm chờ trong ngăn xếp hoặc đã xét xong). Mỗi lần một đỉnh đưa khỏi ngăn xếp ta quay lại xét
phần tử đứng trước nó trong ngăn xếp là ta đã thức hiện một thao tác "quay lui". Như vậy đỉnh đưa
vào đầu tiên bao giờ cũng là đỉnh cuối cùng ra khỏi ngăn xếp.
Thay cho thao tác tìm đỉnh trắng trong danh sách kề với đỉnh u, ta cũng có thể lấy ngay phần tử đầu
tiên v trong danh sách kề Adj(u) (nếu nó khác rỗng) vào ngăn xếp và xóa v khỏi danh sách kề. Khi
đó toàn bộ các đỉnh trong danh sách kề của u đều chưa được xét.
Ta có:
Procedure DFS(G,r){
Var Stack S, list COLOR(u), PARENTS(u)
/*Khởi tạo: Tất cả các đinh chưa xét */
For each' v of E do {
COLOR(u):=WHITE
PARENTS(u):=NULL
}
/* Cho đỉnh đầu tiên vào Stack*/
COLOR(r):=GRAY
Push(S,r)
23
/*lần lượt xét các đỉnh*/

While' S khác rỗng do (
u= End(S); /* Xét phần tử mằm sau cùng trong Stack*/
If Adj(u) khác rỗng then
v=Adj(u)(1)
Delete(Adj(u),v) /*Xóa (tạm thời) v khỏi danh sách kề của u*/
PARENT(v):=u
Push(S,v) /*Bổ sung v vào cuối ngăn xếp*/
else {
COLOR(u)=BLACK
Pos(S,u) /*Lấy u khỏi Stack */
}
}
}
Cây bao trùm nhỏ nhất
Nếu trên tập các cạnh của G có một hàm, được gọi là hàm trọng số, nhận giá trị thực φ(u,v), thì cây
bao trùm có tổng trọng số trên các cạnh nhỏ nhất được gọi là cây bao trùm nhỏ nhất. Có các giải
thuật Prim và Krussal để tìm cây bao trùm ngắn nhất
24

×