Tải bản đầy đủ
Ví dụ 1.11: [2] Thuật toán Kruskal tìm cây khung bé nhất của một đồ thị có V là số đỉnh và E là số cạnh với thời gian O(E log V). Thời gian thực hiện bởi máy Turing cũng cùng bậc như vậy.

Ví dụ 1.11: [2] Thuật toán Kruskal tìm cây khung bé nhất của một đồ thị có V là số đỉnh và E là số cạnh với thời gian O(E log V). Thời gian thực hiện bởi máy Turing cũng cùng bậc như vậy.

Tải bản đầy đủ

20

đa thức, và vì thế không có lời giải đúng nào bằng máy tính (và ta sẽ chỉ đi
tìm lời giải gần đúng).
1.2.5. Lớp bài toán NP-khó (NP-Hard).
Một cách ngắn gọn có thể hiểu bài toán NP-khó là bài toán mà không có
thuật toán thời gian tính đa thức để giải nó trừ khi P = NP, mà chỉ có các thuật
toán giải trong thời gian hàm mũ. Sau đây là định nghĩa chính thức của bài
toán NP-khó.
Định nghĩa 1.22.[1]. Một bài toán A được gọi là NP- khó (NP-hard) nếu như
sự tồn tại thuật toán đa thức để giải nó kéo theo sự tồn tại thuật toán đa thức
để giải mọi bài toán trong NP.
Như vậy mọi bài toán NP-C đều là NP-Hard.
Một số bài toán NP-khó tiêu biểu như:
Bài toán bè cực đại (MaxClique): Cho một đồ thị vô hướng G = (V, E). V là
tập các đỉnh, E là tập các cạnh tương ứng các đỉnh trong V. Cần tìm bè lớn
nhất của G. Bè là tập các đỉnh trong đồ thị mà đôi một có cạnh nối với nhau
(là một đồ thị con đầy đủ trong đồ thị G).
Bài toán phủ đỉnh (Vertex cover): Ta gọi một phủ đỉnh của đồ thị vô hướng
G = (V, E) là một tập con các đỉnh của đồ thị S ⊆ V sao cho mỗi cạnh của đồ
thị có ít nhất một đầu mút trong S. Bài toán đặt ra là: Cho đồ thị vô hướng G
= (V, E) và số nguyên k. Hỏi G có phủ đỉnh với kích thước k hay không?
Một cách không hình thức, có thể nói rằng nếu ta có thể giải được một cách
hiệu quả một bài toán NP-khó cụ thể, thì ta cũng có thể giải hiệu quả bất kỳ
bài toán trong NP bằng cách sử dụng thuật toán giải bài toán NP-khó như một
chương trình con.
Từ định nghĩa bài toán NP-khó có thể suy ra rằng mỗi bài toán NP-đầy đủ
đều là NP-khó. Tuy nhiên một bài toán NP-khó không nhất thiết phải là NPđầy đủ.
Cũng từ bổ đề nêu trên, ta có thể suy ra rằng để chứng minh một bài toán A
nào đó là NP-khó, ta chỉ cần chỉ ra phép qui dẫn một bài toán đã biết là NPkhó về nó.
Từ phần trình bày trên, ta thấy có rất nhiều bài toán ứng dụng quan trọng
thuộc vào lớp NP-khó, và vì thế khó hy vọng xây dựng được thuật toán đúng
hiệu quả để giải chúng. Do đó, một trong những hướng phát triển thuật toán
giải các bài toán như vậy là xây dựng các thuật toán gần đúng.

21

Hình 1.14. Mô hình phân lớp các bài toán

22

Chương 2. MỘT SỐ BÀI TOÁN TỐI ƯU LỚP P, NP-C TRONG ĐỒ THỊ

2.1. Bài toán tìm cây khung bé nhất -Thuật toán Kruskal
Một công ty lập kế hoạch xây dựng mạng truyền thông nối năm trung tâm
máy tính với nhau. Bất kì hai trung tâm nào cũng có thể được nối kết với nhau
bằng đường điện thoại. Cần phải kết nối như thế nào để đảm bảo giữa hai
trung tâm máy tính bất kì luôn có đường truyền thông sao cho tổng số tiền
thuê bao của toàn mạng là tối thiểu? Chúng ta cần mô hình bài toán này bằng
đồ thị có trọng số như hình 2.1, trong đó mỗi đỉnh là một trung tâm máy tính,
mỗi cạnh là một đường truyền thông được thuê bao, còn trọng số của mỗi
cạnh là tiền thuê bao hàng tháng của đường truyền thông được biểu thị bằng
cạnh đó. Có thể giải bài toán này bằng cách tìm cây khung sao cho tổng các
trọng số của các cạnh của cây đạt cực tiểu. Cây khung như thế được gọi là cây
khung nhỏ nhất.
San Francisco
Chicago
New York
Atlanta
Denver
$2000
$1200
$1300
$1400
$900
$700
$1600
$2200
$1000
$800

Hình 2.1. Đồ thị có trọng số biểu thị tiền thuê bao hàng tháng đường truyền
thông trong mạng máy tính
Định nghĩa 2.1.[2] Cây khung nhỏ nhất trong một đồ thị liên thông có trọng
số là cây khung có tổng trọng số trên các cạnh của nó là nhỏ nhất.
Để minh hoạ cho ứng dụng của bài toán cây khung nhỏ nhất, dưới đây ta
nghiên cứu thuật toán Kruskal.
Tư tưởng [6]
Thuật toán do Joseph Kruskal phát minh vào năm 1956. Để thực hiện thuật
toán Kruskal, chọn cạnh có trọng số nhỏ nhất của đồ thị.

23

Lần lượt ghép thêm vào cạnh có trọng số tối thiểu và không tạo thành chu
trình với các cạnh đã được chọn. Thuật toán dừng sau khi (n-1) cạnh đã được
chọn.
Giả sử ta cần tìm cây bao trùm nhỏ nhất của đồ thị G. Thuật toán bao gồm
các bước sau:
• Khởi tạo rừng F (tập hợp các cây), trong đó mỗi đỉnh của G tạo thành một
cây riêng biệt
• Khởi tạo tập S chứa tất cả các cạnh của G
• Chừng nào S còn khác rỗng và F gồm hơn một cây
 Xóa cạnh nhỏ nhất trong S
 Nếu cạnh đó nối hai cây khác nhau trong F, thì thêm nó vào F và hợp hai
cây kề với nó làm một
 Nếu không thì loại bỏ cạnh đó.
Khi thuật toán kết thúc, rừng chỉ gồm đúng một cây và đó là một cây bao
trùm nhỏ nhất của đồ thị G.
Thuật toán Kruskal [2]
Procedure Kruskal (G: đồ thị V đỉnh, liên thông, có trọng số)
T:= đồ thị rỗng
for i := 1 to n-1
begin
e := một cạnh bất kì của G với trọng số nhỏ nhất và không tạo ra chu trình
trong T, khi ghép nó vào T.
T:= T với cạnh e đã được ghép thêm vào.
end { T là cây khung nhỏ nhất}
Độ phức tạp [6]
Nếu E là số cạnh và V là số đỉnh của đồ thị thì thuật toán Kruskal chạy
trong thời gian O(E log V).
Ví dụ 2.1. Cho đồ thị G như hình 2.2, yêu cầu tìm ra cây khung nhỏ nhất của
đồ thị G ?

24

Hình 2.2. Đồ thị có trọng số G
G gồm có 8 đỉnh
Đồ thị G có n phần tử. Thuật toán Kruskal sẽ dừng khi có n-1 trong tập hợp T
n=8
Vậy số cạnh trong tập hợp T: n - 1 = 8 - 1 = 7
Bước 1: Bảng 2.1: Sắp xếp các cạnh theo thứ tự trọng số tăng dần có:
Cạnh

Trọng số

Cạnh (1,5)

1

Cạnh (4, 8)

1

Cạnh (7,8)

1

Cạnh (1, 6)

2

Cạnh (2, 3)

2

Cạnh (3, 8)

3

Cạnh (1, 3)

4

Cạnh (3, 7)

4

Cạnh (4, 5)

5

Cạnh (4, 6)

5

Cạnh (1, 4)

6

Cạnh (5, 6)

6

Cạnh (2, 4)

7

Cạnh (6, 8)

7

Cạnh (1, 2)

8

Cạnh (6, 7)

8

Cạnh (4, 3)

9

và khởi tạo T := Ø.
Bước 2: Duyệt theo cạnh e thuộc danh sách đã sắp xếp
+ Vì T + {(1, 5)} không chứa
chu trình thì ghép cạnh (1,5)
vào cây T:= T + {(1,5)}.

25

+ Vì T + {(4, 8)} không chứa
chu trình thì ghép cạnh (4,8)
vào cây T:= T + {(4, 8)}

+ Vì T + {(7, 8)} không chứa
chu trình thì ghép cạnh (7,8)
vào cây
T:= T + {(7, 8)}

+ Vì T + {(1, 6)} không chứa
chu trình thì ghép cạnh (1,6)
vào cây
T:= T + {(1, 6)}

+ Vì T + {(2, 3)} không chứa
chu trình thì ghép cạnh (2,3)
vào cây
T:= T + {(2, 3)}

26

+ Vì T + {(3, 8)} không chứa
chu trình thì ghép cạnh (3,8)
vào cây
T:= T + {(3, 8)}

+ Vì T + {(1, 3)} không chứa
chu trình thì ghép cạnh (1,3)
vào cây
T:= T + {(1, 3)}

+ Vì T có đủ n -1 cạnh ( 7 cạnh) nên dừng
Cây khung cần tìm có 7 cạnh (đã chọn) và tổng độ dài các cạnh là: 14.
2.2. Bài toán tìm đường đi ngắn nhất -Thuật toán Dijkstra [2]
Trước khi trình bày thuật toán ta xét một ví dụ minh họa.
Ví dụ 2.2. Tính độ dài của đường đi ngắn nhất giữa a và z trong đồ thị có
trọng số cho trên hình 2.3

b
a
2
4
3
d
c
z
e
3

27

3
1
2
Hình 2.3. Đơn đồ thị có trọng số
Ta sẽ tìm độ dài của đường đi ngắn nhất từ a tới các đỉnh kế tiếp cho tới khi
đạt tới đỉnh kế tiếp cho tới khi đạt tới đỉnh z
Chỉ có hai đỉnh b và d liên thuộc với a nên chỉ có hai đường đi xuất phát từ
a là a,b và a,d với các độ dài tương ứng là 4 và 2. Do đó d là đỉnh gần a nhất.
Bây giờ tìm đỉnh tiếp theo gần a nhất trong các đường đi qua a và d ( cho
đến khi đạt đến đỉnh cuối cùng). Đường đi như thế ngắn nhất tới b là a,b với
độ dài 4 và đường đi như thế ngắn nhất tới e là a,d,e, độ dài 5. Do vậy đỉnh
tiếp theo gần a nhất là b.
Để tìm đỉnh thứ ba gần a nhất, ta chỉ xét các đường đi qua a, d và b (cho
đến khi đạt tới đỉnh cuối cùng). Đó là đường đi a,b,c độ dài 7 và đường đi
a,d,e,z độ dài 6. Vậy z là đỉnh tiếp theo gần a nhất và độ dài của đường đi
ngắn nhất từ a tới z là 6
Ví dụ 2.2 minh họa những nguyên tắc chung dung trong thuật toán
Dijkstra. Đường đi ngắn nhất từ đỉnh a tới z có thể tìm được bằng cách kiểm
tra trực tiếp. Nhưng cách làm này là hoàn toàn không dùng được cho cả người
và máy khi đồ thị có nhiều cạnh.
Tư tưởng
Bây giờ sẽ nghiên cứu bài toán tìm độ dài của đường đi ngắn nhất giữa a và
z trong đơn đồ thị liên thông, vô hướng có trọng số. Thuật toán Dijsktra được
thực hiện bằng cách tìm độ dài của đường đi ngắn nhất từ a tới đỉnh thứ hai…
cho tới khi tìm được độ dài của đường đi ngắn nhất từ đỉnh a tới đỉnh z.
Thuật toán dựa trên một dãy các bước lặp. Một tập đặc biệt các đỉnh được
xây dựng bằng cách cộng thêm một đỉnh trong mỗi bước lặp. Thủ tục gán
nhãn được thực hiện trong mỗi lần lặp đó. Trong thủ tục gán nhãn này, đỉnh w
được gán nhãn bằng độ dài đường đi ngắn nhất từ a tới w và chỉ đi qua các
đỉnh đã thuộc tập đặc biệt. Một đỉnh được thêm vào tập này là đỉnh có nhãn
nhỏ nhất so với các đỉnh chưa có trong tập đó.
Bây giờ sẽ đưa ra chi tiết của thuật toán Dijsktra. Đầu tiên, gán cho đỉnh a
nhãn bằng 0 và các đỉnh khác là ∞. Ta kí hiệu L0(a)=0 và L0(v) = ∞ cho tất cả
các nhãn ( bước lặp thứ 0). Các nhãn này là độ dài đường đi ngắn nhất từ đỉnh
a tới các đỉnh này, trong đó đường đi này chỉ chứa đỉnh a.( Vì không có
đường đi từ a tới các đỉnh khác a nên ∞ là độ dài đường đi ngắn nhất giữa a
và các đỉnh này).
Theo thuật toán Dijsktra sẽ xây dựng tập đặc biệt các đỉnh. Gọi Sk là tập
này sau bước lặp thứ k của thủ tục gán nhãn. Chúng ta bắt đầu từ S0= . Tập
Sk được tạo thành từ Sk-1 bằng cách thêm vào đỉnh u không thuộc Sk-1 có nhãn

28

nhỏ nhất. Khi đỉnh u được gộp vào Sk ta sửa đổi nhãn của các đỉnh không
thuộc Sk sao cho Lk(v), nhãn của v tại bước k, là độ dài của đường đi ngắn nhất
từ a tới v mà đường đi này chỉ chứa các đỉnh thuộc Sk ( tức là các đỉnh đã
thuộc tập đặc biệt các đỉnh cùng với u).
Giả sử v là một đỉnh không thuộc Sk . Để sửa nhãn của v ta thấy Lk(v) là độ
dài của đường đi ngắn nhất từ a tới v và chỉ chứa các đỉnh thuộc Sk. Để sửa
đổi nhãn, lưu ý rằng đường đi ngắn nhất từ a tới v chỉ chứa phần tử của Sk
hoặc là đường đi ngắn nhất từ a tới v chỉ chứa các phần tử của Sk-1 hoặc là
đường đi ngắn nhất từ a tới u ở bước k-1 cùng với cạnh (u,v). Nói cách khác
ta có:
Lk(a,v) = min {Lk-1(a,v), Lk-1(a,u) + w(u,v)} .
Thủ tục này được lặp bằng cách liên tiếp thêm các đỉnh vào tập đặc biệt các
đỉnh cho tới khi đạt tới đỉnh z. Khi thêm z vào tập đặc biệt các đỉnh thì nhãn
của nó bằng độ dài của đường đi ngắn nhất từ a tới z.
Thuật toán Dijsktra[2]
procedure Dijsktra (G: đơn đồ thị liên thông có trọng số, với trọng số
dương)
{ G có các đỉnh a = v0, v1, …, vn= z và trọng số w(vi,vj), với w(vi,vj) = ∞
nếu { vi, vj } không là một cạnh trong G}
for i:= 1 to n
L(vi) := ∞
L(a):= 0
S:=
{Ban đầu các nhãn được khởi tạo sao cho nhãn của a bằng 0, các đỉnh
khác = ∞, tập S là rỗng}
while z S
begin
u:= đỉnh không thuộc S có nhãn L(u) nhỏ nhất.
S:= S {u}
for tất cả các đỉnh v không thuộc S
if L(u) + w(u,v) < L(v) then L(v) := L(u) + w(u,v)
{thêm vào S đỉnh có nhãn nhỏ nhất và sửa đổi nhãn của các đỉnh
không thuộc S}
end { L(z) = độ dài đường đi ngắn nhất từ a tới z }
Độ phức tạp. [7]
Thuật toán Dijkstra tìm được đường đi ngắn nhất trên đồ thị sau thời gian
cỡ O(n2 ).
Ví dụ 2.3. Dùng thuật toán Dijsktra hãy tìm độ dài đường đi ngắn nhất giữa
hai đỉnh a và z của đồ thị có trọng số trên hình 2.4(a)
Các bước dùng thuật toán Dijsktra tìm độ dài của đường đi ngắn nhất giữa
hai đỉnh a và z được biểu diễn trên hình 2.4. Tại mỗi bước lặp của thuật toán
các đỉnh của tập S được khoanh tròn. Đường đi ngắn nhất chỉ chứa các đỉnh

29

đã thuộc Sk từ a tới mỗi đỉnh được in ra cho mỗi bước lặp. Thuật toán kết thúc
khi z được khoanh tròn. Ta nhận được đường đi ngắn nhất từ a tới z là a ,c, b,
d, e, z với độ dài bằng 13( hình 2.4 (g))
a
0
4
1
6
c

b

8
10
e

d

5
2
z
3
2


(a)
a
0
4
1
6

30

c
2(a)
b
4(a)
8
10
e

d

5
2
z
3
2


(b)
a
0
4
1
6
c
2(a)
b
3(a,c)
8
10