Fb.com/vanvananh.anan
Bài toán tối ưu là một bài toán muôn thưở của ngành tin học bởi nó có ứng dụng hết
sức rộng rãi và đa dạng. Các bài toán tối ưu thường xuất hiện trong các ngành kinh tế
và kĩ thuật cũng vì lý do này mà trên thế giới có rất nhiều thuật toán để giải các bài
toán tối ưu. Một trong số đó có thể kể đến là thuật toán Prim
I.
GIỚI THIỆU THUẬT TOÁN PRIM
1. Bài toán cây khung nhỏ nhất
Bài toán cây khung (cây bao trùm) nhỏ nhất của đồ thị là một trong số những
bài toán tối ưu trên đồ thị tìm được ứng dụng trong nhiều lĩnh vực khác nhau của đời
sống. Để minh họa cho những ứng dụng đó, chúng ta cùng tham khảo ba mô hình thực
tế tiêu biểu của bài toán:
• Bài toán xây dựng hệ thống cable: Giả sử muốn xây dựng một hệ thống cable
điện thoại nối n điểm của một mạng viễn thông sao cho điểm bất kì nào trên
mạng đều có đường truyền tin tới các điểm khác. Biết chi phí xây dựng hệ
thống cable từ điểm i đến điểm j là c[i,j]. Hãy tìm cách xây dựng hệ thống mạng
cable sao cho chi phí là nhỏ nhất
• Bài toán xây dựng hệ thống đường sắt: Giả sử ta muốn xây dựng một hệ
thống đường sắt nối n thành phố sao cho hành khách có thể đi từ bất kỳ một
thành phố nào đến bất kỳ một trong các thành phố còn lại. Mặt khác trên
quan điểm kinh tế đòi hỏi là chi phí xây dựng hệ thống đường phải nhỏ nhất.
Rõ ràng đồ thị mà đỉnh là các thành phố còn các cạnh là các tuyến đường sắt
nối các thành phố tương ứng với phương án xây dựng tối ưu phải là cây. Vì
vây, bài toán đặt ra dẫn về bài toán tìm cây khung nhỏ nhất trên đồ thị đầy đủ
n đỉnh, mỗi đỉnh tương ứng với một thành phố, với độ dài trên các các cạnh
chính là chi phí xây dựngđường ray nối hai thành phố tương ứng (chú ý là trong
1
Fb.com/vanvananh.anan
bài toán này ta giả thiết là không xây dựng tuyến đường sắt có các nhà ga phân
tuyến nằm ngoài các thành phố).
•
Bài toán nối mạng máy tính: Cần nối mạng một hệ thống gồm n máy tính đánh
số từ 1 đến n. Biết chi phí nối máy i với máy j là c[i,j], i,j = 1, 2, . . . ,n ( thông
thường chi phí này phụ thuộc vào độ dài cáp nối cần sử dụng). Hãy tìm cách
nối mạng sao cho tổng chi phí nối mạng là nhỏ nhất.
Và bài toán cây khung nhỏ nhất được phát biểu như sau:
Cho đồ thị G=<V,E> là đồ thị vô hướng liên thông với tập đỉnh V={1,2,3…,n}
và tập cạnh E gồm m cạnh. Mỗi cạnh e của đồ thị được gán với một sốc(e) được gọi là
trọng số của cạnh. Giả sử H=<V,T> là một cây khung của đồ thị G. Ta gọi độ dài c(H)
của cây khung H là tổng độ dài các cạnh :
c(H)= ∑
e∈ Tc(e)
Bài toán đặt ra là, trong số các cây khung của đồ thị hãy tìm cây khung có độ dài
nhỏ nhất của đồ thị.
Để giải bài toán tìm cây khung nhỏ nhất, chúng ta có thể liệt kê toàn bộ cây khung
và chọn trong số những cây khung đã liệt kê một cây khung có độ dài nhỏ nhất. Tuy
nhiên số cây khung của mỗi đồ thị lại rất lớn cỡ n n-2 do vậy phương án này hoàn toàn
không khả thi và chỉ cần với đồ thị với số đỉnh cỡ chục thì phương án này trở nên vô
dụng.
Rất may là chúng ta đã có hai thuật toán vô cùng hiệu quả để giải quyết bài toàn
trên đó chính là thuật toán Prim và thuật toán Kruskal.
Chúng ta sẽ cùng tìm hiểu về thuật toán Prim.
2. Sự ra đời của thuật toán Prim
Thuật toán Prim còn được mang tên là người láng giềng gần nhất, hay phương
pháp lân cận gần nhất,là một thuật toán tham lam để tìm cây khung nhỏ nhất của
một đồ thị vô hướng có trọng số liên thông. Nghĩa là nó tìm một tập hợp các cạnh của
đồ thị tạo thành một cây chứa tất cả các đỉnh, sao cho tổng trọng số các cạnh của cây
là nhỏ nhất.
2
Fb.com/vanvananh.anan
Thuật toán được tìm ra năm 1930 bởi nhà toán học người Séc Vojtěch Jarník và
sau đó bởi nhà nghiên cứu khoa học máy tính Robert C. Prim năm 1957 và một lần
nữa độc lập bởi Edsger Dijkstra năm 1959. Do đó nó còn có tên gọi là thuật toán
DJP, thuật toán Jarník, hay thuật toán Prim–Jarník.
II.
MÔ TẢ THUẬT TOÁN
1. Ý 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ụ thể như sau:
Bắt đầu từ một đỉnh tùy ý của đồ thị, nối đỉnh đó với một đỉnh thứ hai sao cho
trọng số của cạnh nối hai đỉnh trên là nhỏ nhất. Tiếp theo, từ một trong hai đỉnh đó,
tìm cạnh có độ dài nhỏ nhất, điều này dẫn tới một đỉnh thứ ba và như vậy chúng ta đã
thu được một cây bộ phận gồm 3 đỉnh và 2 cạnh. Quá trình này được tiếp tục cho tới
khi ta nhận được cây gồm n-1 cạnh, đó chính là cây khung nhỏ nhất cần tìm
Ta có thể thấy rõ hơn qua ví dụ bằng hình vẽ sau:
20
33
8
2
18
1
4
16
17
14
3
Bắt đầu từ đỉnh 1
3
6
5
4
5
Fb.com/vanvananh.anan
4
Fb.com/vanvananh.anan
5
Fb.com/vanvananh.anan
6
Fb.com/vanvananh.anan
2. Mô tả
Begin.
Bước 1 (Khởi tạo):
VH = {s}; //Tập đỉnh cây khung thiết lập ban đầu là s
V=V\{s}; //Tập đỉnh V được bớt đi s
T=∅; //Tập cạnh cây khung thiết lập ban đầu là ∅
D(H)=0;//Độ dài cây khung được thiết lập là 0
Bước 2 (Lặp):
while(V≠∅) do{
e=<u,v>; //cạnh có độ dài nhỏ nhất thỏa mãn u∈V, v∈VH
d(H)=d(H)+d(e);//thiết lập độ dài cây khung nhỏ nhất
T=T∪ {e};//Kết nạp e vào cây khung
V=V\{u};//Tập đỉnh V bớt đi đỉnh u
VH=VH∪{u};//Tập đỉnh VH thêm vào đỉnh u
}
Endwhile;
7
Fb.com/vanvananh.anan
Bước 3 (Trả lại kết quả):
if(|T|
else Return(T,d(H));
End.
3. Kiểm nghiệm thuật toán
Cho đồ thị G gồm 13 đỉnh được biểu diễn dưới dang ma trận kề như dưới đây.
Trong đó các cạnh có trọng số bằng 35000 tức không đường đi giữa hai đỉnh của cạnh
đó
13
35000 2 1 3 35000 35000 35000 35000 35000 35000 35000 35000 35000
2 35000 2 35000 35000 5 5 35000 35000 35000 35000 35000 35000
1 2 35000 4 35000 5 35000 35000 35000 35000 35000 35000 35000
3 35000 4 35000 5 5 35000 35000 35000 35000 35000 35000 35000
35000 35000 35000 5 35000 6 35000 35000 35000 6 35000 35000 35000
35000 5 5 5 6 35000 6 6 6 6 35000 35000 35000
35000 5 35000 35000 35000 6 35000 6 35000 35000 35000 35000 35000
35000 35000 35000 35000 35000 6 6 35000 7 35000 35000 7 7
35000 35000 35000 35000 35000 6 35000 7 35000 7 7 35000 35000
35000 35000 35000 35000 6 6 35000 35000 7 35000 7 7 35000
35000 35000 35000 35000 35000 35000 35000 35000 7 7 10000 8 10000
35000 35000 35000 35000 35000 35000 35000 7 35000 7 8 35000 8
35000 35000 35000 35000 35000 35000 35000 7 35000 35000 35000 8 35000
Khi đó các bước của thuật toán Prim được thực hiện như dưới đây:
Bước khởi tạo: T=∅; D(T)=0; V=2,3,4,5,6,7,8,9,10,11,12,13; VH=2
E=(v,t)|
u∈V,t∈VT có độ
dài nhỏ nhất
V\v=?
VH∪ v=?
T, D(T)
(1,3)
2,4,5,6,7,8,9,10,11,12,1
3
1,3
T=T∪(1,3)
D(T)=0+1=1
(1,2)
4,5,6,7,8,9,10,11,12,13
1,2,3
T=T∪(1,2)
D(T)=1+2=3
(1,4)
5,6,7,8,9,10,11,12,13
1,2,3,4
T=T∪(1,4)
D(T)=3+3=6
(2,6)
5,7,8,9,10,11,12,13
1,2,3,4,6
T=T∪(2,6)
8
Fb.com/vanvananh.anan
D(T)=6+5=11
(2,7)
5,8,9,10,11,12,13
1,2,3,4,6,7
T=T∪(2,7)
D(T)=11+5=1
6
(4,5)
8,9,10,11,12,13
1,2,3,4,5,6,7
T=T∪(4,5)
D(T)=16+5=2
1
(5,10)
8,9,11,12,13
1,2,3,4,5,6,7,10
T=T∪(5,10)
D(T)=21+6=2
7
(6,8)
9,11,12,13
1,2,3,4,5,6,7,8,10
T=T∪(6,8)
D(T)=27+6=3
3
1,2,3,4,5,6,7,8,9,10
T=T∪(6,9)
D(T)=33+6=3
9
(6,9)
11,12,13
(8,12)
11,13
1,2,3,4,5,6,7,8,9,10,12
T=T∪(8,12)
D(T)=39+7=4
6
(8,13)
11
1,2,3,4,5,6,7,8,9,10,12,13
T=T∪(8,13)
D(T)=46+7=5
3
∅
1,2,3,4,5,6,7,8,9,10,12,13,
11
T=T∪(9,11)
D(T)=53+7=6
0
(9,11)
V=∅kết thúc bước lặp
Kếtquả: T={(1,3),(1,2),(1,4),(2,6),(2,7),(4,5),(5,10),(6,8),(6,9),(8,12),(8,13),(9,11)}
D(T)=1+2+3+5+5+5+5+6+6+6+7+7+7=60
III.
ĐÁNH GIÁ THUẬT TOÁN
1. Độ phức tạp thuật toán
Xét về độ phức tạp thuật toán sử dụng ma trận kề, thuật toán Prim có độ phức tạp
là O(n2). 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 có độ
phức tạp O((m+n)lgn).
9
Fb.com/vanvananh.anan
Do thời gian nghiên cứu hạn hẹp nên chúng tôi xin phép chỉ trình bày cách lập
trình sử dụng ma trận kề.
2. Tính đúng đắn của thuật toán.
Chứng minh tính đúng đắn của thuật toán:
Đặt G là một đồ thị có trọng số liên thông.
Trong mỗi bước, thuật toán Prim chọn một cạnh nối một đồ thị con với một đỉnh
không nằm trong đồ thị con đó. Mặt khác, do G liên thông nên ta luôn tìm được đường
đi từ mỗi đồ thị con tới tất cả các đỉnh còn lại, và cạnh mới thêm không bao giờ tạo
thành chu trình với các cạnh cũ. Từ đó ta có thể kết luận được rằng kết quả cuối cùng
T là một cây.
Đặt T1 là một cây bao trùm nhỏ nhất của G.Nếu T1=T thì T là cây bao trùm nhỏ
nhất.
Ngược lại, nếu không, đặt e là cạnh đầu tiên được đưa vào cây khung T mà không
thuộc cây khung T1, còn VTlà tập hợp các đỉnh thuộc T khi ta chưa thêm cạnh e. Một
đầu của e thuộc VT và đầu kia không thuộc VT. Vì T1 là một cây bao trùm của G, nên
tồn tại đường đi trong T1 nối hai đầu của e. Trên đường đi đó, nhất định tồn tại cạnh e’
nối một đỉnh trong VT với một đỉnh ngoài VT. Trong bước lặp khi e được thêm vào Y,
thuật toán cũng có thể chọn e’ thay vì e nếu như trọng số của nó nhỏ hơn e.
Vì e’ không được chọn nên: d[e’]>d[e]
Đặt T2 là đồ thị thu được bằng cách xóa e’ và thêm e vào T1. Do T2 liên thông, có
cùng số cạnh như T1, và tổng trọng số các cạnh không quá trọng số của T1, nên nó
cũng là một cây bao trùm nhỏ nhất của G và nó chứa e cũng như tất cả các cạnh được
thuật toán chọn trước nó.
Cứ lặp lại lập luận trên nhiều lần, cuối cùng ta thu được một cây bao trùm nhỏ
nhất của G giống hệt như T
Vì vậy T là một cây bao trùm nhỏ nhất
3. Ưu điểm so với Kruskal
10
Fb.com/vanvananh.anan
Thuật toán Kruskal làm việc kém hiệu quả với những đồ thị dày (số cạnh cỡ
m≈n(n-1)/2). Trong trường hợp đó thuật toán Prim tỏ ra hiệu quả hơn. Tuy nhiên khi
làm việc với đồ thị thưa người ta thường lựa chọn thuật toán Kruskal thay vì thuật toán
Prim.
IV.
CÀI ĐẶT THUẬT TOÁN
1. Lưu đồ thuật toán
//lệnh if cần bổ sung i chạy i++ vào lưu đồ
INPUT
g[i][j],n
N[],u=1,d[],vt[]
Đ
d[v]=35000
vt[u]=1
N[i]=14
int i=1
S
i<=n
S
vt[u]=0
i=1
i
Đ
1
S
u=0 Đ
v=2
S
Nmin=14
v<=n
dmin=35000
Svt[v]=1
v=2
S
Đ dmin>=d[v]
v<=nn
Nmin>N[v]
Đ
0
2. Chương trình
11
u=v
Nmin=N[v]
dmin=d[v]
Đ
vt[v]=1,g[u][v]≠35000
d[v]>=g[u][v]
d
Đ
d[v]=g[u]
[v]
Fb.com/vanvananh.anan
Chương trình tìm cây khung nhỏ nhất theo thuật toán PRIM cho đồ thị biểu diễn
dưới dạng ma trận kề được thể hiện dưới đây với các hàm:
•
•
•
•
read(): kiểm tra đã tồn tại file Input
readFile(): đọc File Input
Prim(): thuật toán Prim xây dựng cây khung nhỏ nhất
Result: đưa tập cạnh và độ dài nhỏ nhất của cây khung
Chương trình được thực hiện như sau:
#include<iostream>
#include<fstream>
#define fin "PRIM.INP"
#define fon "PRIM.OUT"
using namespace std;
ifstream ifs;
ofstream ofs;
bool read();//Kiem tra da ton tai file Input chua
void readFile(int G[][100],int &n);//Tien hanh doc file
bool Prim(int G[][100],int n,int N[]);//Thuc hien thuat toan Prim
void result(int G[][100],int N[100],int n);//In ket qua
main(){
int G[100][100],n,N[100];
if(read()){
readFile(G,n);
if(Prim(G,n,N)){
result(G,N,n);
}
else{
12
Fb.com/vanvananh.anan
cout<<"Do Thi Khong Lien Thong!";
}
}
else{
cout<<"File not Found!";
}
return 0;
}
bool read(){
ifs.open(fin,ios::in);
if(!ifs){
return 0;
}
return 1;
}
void readFile(int G[][100],int &n){
ifs>>n;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++) {
ifs>>G[i][j];
}
}
ifs.close();
}
bool Prim(int G[][100],int n, int N[]){
13
Fb.com/vanvananh.anan
int vt[n],d[n],u=1;
for(int i=1;i<=n;i++){
vt[i]=1;//gan cac tat ca cac dinh chua xet bang 1
d[i]=35000;
N[i]=14;
}
vt[u]=0;// dinh u da xet gan bang 0
for(int i=1;i
for(int v=2;v<=n;v++){
if(vt[v]&&(G[u][v]!=35000)&&(d[v]>=G[u][v])&&(u
d[v]=G[u][v];//dua trong so cua cac canh vao mang d
N[v]=u;//canh ke cua u la v
}
}
u=0;
int dmin=35000;
int Nmin=14;//dinh co chi so nho nhat
for(int v=2;v<=n;v++){
if(vt[v]&&(d[v]<=dmin)&&(N[v]<=Nmin)){//tim dinh chua xet
noi voi tao canh be nhat va co chi so be nhat
Nmin=N[v];
dmin=d[v];//tim canh co trong so nho nhat
u=v;//gan canh u la canh co trong so nho nhat
}
}
14
Fb.com/vanvananh.anan
vt[u]=0;
if(u==0){
return 0;
}
}
return 1;
}
void result(int G[][100],int N[100],int n){
int D=0;
ofs.open(fon,ios::out);
ofs<<"Cay khung ngan nhat la: "<
for(int i=1; i
for(int v=2;v<=n;v++){
if(N[v]==i){
ofs<<"("<
ofs<
ofs<
D+= G[v][N[v]];
}
}
}
ofs<
ofs<<"Tong trong so la: "<
}
3. Chạy thử
15
Fb.com/vanvananh.anan
Input: (PRIM.INP)//G[i][j]=35000 tức không có đường đi từ i đến j (G[i][j]= ∞)
13
35000 2 1 3 35000 35000 35000 35000 35000 35000 35000 35000 35000
2 35000 2 35000 35000 5 5 35000 35000 35000 35000 35000 35000
1 2 35000 4 35000 5 35000 35000 35000 35000 35000 35000 35000
3 35000 4 35000 5 5 35000 35000 35000 35000 35000 35000 35000
35000 35000 35000 5 35000 6 35000 35000 35000 6 35000 35000 35000
35000 5 5 5 6 35000 6 6 6 6 35000 35000 35000
35000 5 35000 35000 35000 6 35000 6 35000 35000 35000 35000 35000
35000 35000 35000 35000 35000 6 6 35000 7 35000 35000 7 7
35000 35000 35000 35000 35000 6 35000 7 35000 7 7 35000 35000
35000 35000 35000 35000 6 6 35000 35000 7 35000 7 7 35000
35000 35000 35000 35000 35000 35000 35000 35000 7 7 35000 8 35000
35000 35000 35000 35000 35000 35000 35000 7 35000 7 8 35000 8
35000 35000 35000 35000 35000 35000 35000 7 35000 35000 35000 8 35000
Màn hình:
Kết quả được hiển thị trong file PRIM.OUT
16
Fb.com/vanvananh.anan
4. Kiểm nghiệm
Một số bộ test khác:
Input:15
35000 2 1 3 35000 35000 35000 35000 35000 35000 35000 35000 35000 3 2
2 35000 2 35000 35000 5 5 35000 35000 35000 35000 35000 35000 1 3
1 2 35000 4 35000 5 35000 35000 35000 35000 35000 35000 35000 4 35000
3 35000 4 35000 5 5 35000 35000 35000 35000 35000 35000 35000 1 35000
35000 35000 35000 5 35000 6 35000 35000 35000 6 35000 35000 35000 2 2
35000 5 5 5 6 35000 6 6 6 6 35000 35000 35000 2 7
35000 5 35000 35000 35000 6 35000 6 35000 35000 35000 35000 35000 4 6
35000 35000 35000 35000 35000 6 6 35000 7 35000 35000 7 7 7 8
35000 35000 35000 35000 35000 6 35000 7 35000 7 7 35000 35000 35000 1
35000 35000 35000 35000 6 6 35000 35000 7 35000 7 7 35000 3 4
35000 35000 35000 35000 35000 35000 35000 35000 7 7 35000 8 35000 2 5
35000 35000 35000 35000 35000 35000 35000 7 35000 7 8 35000 8 1 4
17
Fb.com/vanvananh.anan
35000 35000 35000 35000 35000 35000 35000 7 35000 35000 35000 8 35000 2 35000
Màn hình:
Input:15
35000 2 1 3 35000 35000 35000 35000 35000 35000 35000 35000 35000 35000 35000
2 35000 2 35000 35000 5 5 35000 35000 35000 35000 35000 35000 35000 35000
1 2 35000 4 35000 5 35000 35000 35000 35000 35000 35000 35000 35000 35000
3 35000 4 35000 5 5 35000 35000 35000 35000 35000 35000 35000 35000 35000
35000 35000 35000 5 35000 6 35000 35000 35000 6 35000 35000 35000 35000 35000
35000 5 5 5 6 35000 6 6 6 6 35000 35000 35000 35000 35000
35000 5 35000 35000 35000 6 35000 6 35000 35000 35000 35000 35000 35000 35000
35000 35000 35000 35000 35000 6 6 35000 7 35000 35000 7 7 35000 35000
35000 35000 35000 35000 35000 6 35000 7 35000 7 7 35000 35000 35000 35000
35000 35000 35000 35000 6 6 35000 35000 7 35000 7 7 35000 8 35000
35000 35000 35000 35000 35000 35000 35000 35000 7 7 35000 8 35000 35000 9
35000 35000 35000 35000 35000 35000 35000 7 35000 7 8 35000 8 35000 35000
35000 35000 35000 35000 35000 35000 35000 7 35000 35000 35000 8 35000 35000 35000
35000 35000 35000 35000 35000 35000 35000 35000 35000 8 35000 35000 35000 35000 35000
35000 35000 35000 35000 35000 35000 35000 35000 35000 35000 9 35000 35000 35000 35000
Kết quả:
18
Fb.com/vanvananh.anan
KẾT LUẬN
Thông qua bài tiểu luận, chúng tôi đã hiểu về thuật toán Prim cũng như nâng
cao khả năng làm làm việc nhóm của bản thân. Bài tiểu luận còn nhiều thiếu xót, rất
mong nhận được những đánh giá và góp ý từ phía thầy giáo và các bạn.
Chúng tôi xin chân thành cảm ơn.
Nhận xét của thầy giáo:
……………………………………………………………………………………………………………
……………………………………………………………………………………………………………
……………………………………………………………………………………………………………
……………………………………………………………………………………………………………
…………………………………………………………………………………………………..
……………………………………………………………………………………………………………
…
19
Fb.com/vanvananh.anan
TÀI LIỆU THAM KHẢO
1. Giải Thuật Và Lập Trình_LÊ MINH HOÀNG.
2. Giáo Trình Toán Rời Rạc_Học Viện Công Nghệ Bưu Chính Viễn Thông.
3. Từ Internet.
20
Fb.com/vanvananh.anan
MỤC LỤC
I.
GIỚI THIỆU THUẬT TOÁN PRIM………………………………………… 1
1. Bài toán cây khung nhỏ nhất …………………………………………….. 2
2. Sự ra đời của thuật toán Prim…………………………………………….. 3
II.
MÔ TẢ THUẬT TOÁN
1. Ý tưởng ……………………………………………………………………. 3
2. Mô tả………………………………………………………………….…….. 4
3. Kiểm nghiệm thuật toán…………………………………………………..
III.
ĐÁNH GIÁ THUẬT TOÁN
1. Độ phức tạp…………………………………………………..……………..
2. Tính đúng đắn của thuật toán…………………………………………….
3. Ưu điểm so với thuật toán Kruskal……………………..…………………
IV.
CÀI ĐẶT CHƯƠNG TRÌNH
5
6
7
7
8
1. Lưu đồ thuật toán……………………………………….…………………. 9
2. Chương trình……………………………………………………………….. 12
3. Chạy thử……………………………………………………………….…… 14
Một số các test khác……………………………………………………….…………..
21