Professional Documents
Culture Documents
1
Nội dung
2
1.Các giải thuật đồ thị căn bản
Có nhiều bài toán được định nghĩa theo đối tượng và các
kết nối giữa các đối tượng ấy.
Một đồ thị là một đối tượng toán học mà mô tả những bài
toán như vậy.
Các ứng dụng trong các lãnh vực:
Giao thông
Viễn thông
Điện lực
Mạng máy tính
Cơ sở dữ liệu
Trình biên dịch
Các hệ điều hành
Lý thuyết đồ thị
3
Một thí dụ
H I
B C G
D E J K
L M
4
Thuật ngữ
Một đồ thị là một tập các đỉnh và các cạnh. Các đỉnh là
những đối tượng đơn mà có thể có tên và có một số tính
chất khác và cạnh là đường kết nối giữa hai đỉnh.
5
Thuật ngữ (tt.)
Một chu trình (cycle ) là một lối đi đơn ngoại trừ đỉnh đầu
tiên và đỉnh cuối cùng trùng nhau (một lối đi từ một đỉnh
quay về chính nó).
Một đồ thị không có chu trình được gọi là cây (tree). Một
nhóm các cây không liên thông được gọi là rừng ( forest ).
Cây bao trùm của một đồ thị là một đồ thị con mà chứa tất cả
các đỉnh trong cây nhưng một số cạnh đủ chạm đến mọi đỉnh.
Gọi số đỉnh trong một đồ thị là V, số cạnh là E, số cạnh của
đồ thị có thể có từ 0 đến V (V-1)/2. (Chứng minh truy chứng).
Đồ thị có tất cả mọi cạnh hiện diện giữa mọi cặp đỉnh được
gọi là đồ thị đầy đủ (complete graphs).
6
Thuật ngữ (tt.)
Các đồ thị với số cạnh tương đối ít được gọi là đồ thị thưa;
các đồ thị với chỉ một số ít cạnh mất đi được gọi là đồ thị dày.
7
Cách biểu diễn đồ thị
8
Cách biểu diễn ma trận kế cận
A B C D E F G H I J K L M
Một ma trận V
A 1 1 1 0 0 1 1 0 0 0 0 0 0
B 1 1 0 0 0 0 0 0 0 0 0 0 0 hàng V cột chứa
C 1 0 1 0 0 0 0 0 0 0 0 0 0 các giá trị Boolean
D 0 0 0 1 1 1 0 0 0 0 0 0 0 mà a[x, y] là true if
E 0 0 0 1 1 1 1 0 0 0 0 0 0 nếu tồn tại một
F 1 0 0 1 1 1 0 0 0 0 0 0 0
cạnh từ đỉnh x đến
G 1 0 0 0 1 0 1 0 0 0 0 0 0
H 0 0 0 0 0 0 0 1 1 0 0 0 0 đỉnh y và false nếu
I 0 0 0 0 0 0 0 1 1 0 0 0 0 ngược lại.
J 0 0 0 0 0 0 0 0 0 1 1 1 1
K 0 0 0 0 0 0 0 0 0 1 1 0 0 Hình 4.1b: Ma trận kế
L 0 0 0 0 0 0 0 0 0 1 0 1 1 cận của đồ thị ở hình
M 0 0 0 0 0 0 0 0 0 1 0 1 1 4.1a
9
Ghi chú về cách biểu diễn ma trận kế cận
10
Giải thuật
program adjmatrix (input, output);
const maxV = 50;
var j, x, y, V, E: integer;
a: array[1..maxV, 1..maxV] of boolean;
begin
readln (V, E);
for x: = 1 to V do /*initialize the matrix */
for y: = 1 to V do a[x, y]: = false;
for x: = 1 to V do a[x, y]: = true;
for j: = 1 to E do
begin
readln (v1, v2);
x := index(v1); y := index(v2);
a[x, y] := true; a[y, x] := true
end;
end.
11
Cách biểu diễn bằng tập danh sách kế cận
Trong cách biểu diễn này, mọi đỉnh mà nối tới một
đỉnh được kết thành một danh sách kế cận
(adjacency-list ) cho đỉnh đó.
12
begin
readln(V, E);
new(z); z↑.next: = z;
for j: = 1 to V do adj[j]: = z;
for j: 1 to E do
begin
readln(v1, v2);
x: = index(v1); y: = index(v2);
new(t); t↑.v: = x; t↑.next: = adj[y];
adj[y]: = t; /* insert x to the first element of
y’s adjacency list */
new(t); t↑.v = y; t↑.next: = adj[x];
adj[x]:= t; /* insert y to the first element of
x’s adjacency list */
end;
end.
13
a b c d e f g h i j k l m
f a a f g a e i h k j j j
c e f e a l m l
b d d m
15
Duyệt theo chiều sâu trước – giải thuật đệ quy
procedure dfs;
procedure visit(n:vertex);
begin
add n to the ready stack;
while the ready stack is not empty do
get a vertex from the stack, process it,
and add any neighbor vertex that has not been processed
to the stack.
if a vertex has already appeared in the stack, there is no
need to push
it to the stack, but it is moved to the top of the stack.
end;
begin
Initialize status;
for each vertex, say n, in the graph do
if the status of n is “not yet visited” then visit(n)
end;
16
Tìm kiếm theo chiều sâu trước – biểu diễn
danh sách kế cận
procedure list-dfs;
var id, k: integer;
val: array[1..maxV] of integer;
procedure visit (k: integer);
var t: link;
begin
id: = id + 1; val[k]: = id; /* change the status of k
to “visited” */
t: = adj[k]; / * find the neighbors of the vertex k */
while t <> z do
begin
if val[t ↑.v] = 0 then visit(t↑.v);
t: = t↑.next
end
end;
17
begin
id: = 0;
for k: = 1 to V do val[k]: = 0; /initialize
the status of all vetices */
for k: = 1 to V do
if val[k] = 0 then visit(k)
end;
18
A A A A
F F F
A A A A
G G G G
E E E E
F F F F
A A A A
G G G G
D E D E D E D E
F F F F
A A A A
C G C G B C G B C G
D E D E D E D E
F F F F
19
Hình 4.2 Duyệt theo chiều sâu trước
Như vậy kết quả của giải thuật duyệt DFS trên đồ thị cho ở
hình 4.1a với tập danh sách kế cận cho ở hình 4.1c là
AFEGDCG
Lưu ý: thứ tự của các đỉnh trong các danh sách kế cận có ảnh
hưởng đến thứ tự duyệt của các đỉnh khi áp dụng DFS.
20
Độ phức tạp của duyệt theo chiều sâu trước
21
Duyệt theo chiều sâu trước – biểu diễn bằng ma
trận kế cận
23
Duyệt theo chiều sâu trước – không đệ quy
procedure list-dfs;
var id, k: integer; val: array[1..max V] of integer;
procedure visit(k: integer);
var t: link;
begin
push(k);
repeat
k: = pop; id:= id + 1; val[k]: = id; /* change the status of k to “visited” */
t =: adj[k]; /* find the neighbors of the vertex k */.
while t <> z do
begin
if val[t↑.v] = 0 then
begin
push(t↑.v); val[t↑.v]: = -1 /* change the status of t↑.v to “ready” */
end
else if val[t↑.v] = -1 then shift t↑.v to the top of the stack;
t: = t↑.next
end
until stackempty
end;
24
begin
id: = 0; stackinit;
for k: = 1 to V do val[k]: = 0; /* initialize the
status of all vertices */
for k: = 1 to V do
if val[k] = 0 then visit(k)
end;
Với giải thuật không đệ quy, ta cần dùng một stack được gọi
là ready stack.
Ghi chú:
val[k] = 0 nếu đỉnh k là “chưa được viếng thăm”,
val[k] = -1 nếu đỉnh k đang ở trong ready stack
val[k] là một trị dương nếu đỉnh k đã được viếng thăm.
25
A A
B C G
A A
B C G B C G
E D E
F F
26
G E D
B B B B M
C C C C C L L
A F F F F F F H I J K K K
Hình 4.3b Nội dung của stack khi thực hiện duyệt theo chiều sâu trước
27
Duyệt theo chiều rộng trước
Khi duyệt đồ thị nếu ta dùng một queue thay vì một stack, ta sẽ
đi đến một giải thuật duyệt theo chiều rộng trước (breadth-first-
search).
procedure bfs;
procedure visit(n: vertex);
begin
add n to the ready queue;
while the ready queue is not empty do
get a vertex from the queue, process it, and add any neighbor
vertex that has not been processed to the queue and
change their status to ready.
end;
begin
Initialize status;
for each vertex, say n, in the graph
if the status of n is “not yet visited” then visit(n)
end;
28
procedure list-bfs;
var id, k: integer; val: array[1..max V] of integer;
procedure visit(k: integer);
var t: link;
begin
put(k); /* put a vertex to the queue */
repeat
k: = get; /* get a vertex from the queue */
id: = id + 1; val[k]: = id; /* change the status of k to “visited” */
t: = adj[k]; /* find the neighbors of the vertex k */
while t <> z do
begin
if val[t ↑.v] = 0 then
begin
put(t↑.v); val [t↑.v]: = -1 /* change the vertex t↑.v to “ready” */
end;
t: = t↑.next
end
until queueempty
end;
29
begin
id: = 0; queue-initialze;
for k: = 1 to V do val[k]: = 0; /initialize the
status of all vertices */
for k: = 1 to V do
if val[k] = 0 then visit(k)
end;
Duyệt theo chiều sâu trước và duyệt theo chiều rộng trước
chỉ khác nhau ở chỗ giải thuật đầu dùng stack và giải thuật
sau dùng hàng đợi. Do đó, độ phức tạp tính toán của DFS và
BFS là như nhau.
30
I
D D D D D
E E E E
G G G G
B B B M M M
C C L L
F K
A J
Hình 4.4 Nội dung của hàng đợi khi thực hiện BFS
31
2. Đồ thị có trọng số
Bài toán thứ nhất là bài toán cây bao trùm tối thiểu; bài toán
thứ hai là bài toán tìm đường đi ngắn nhất.
32
Cây bao trùm tối thiểu
Một cây bao trùm tối thiểu (minimum spanning tree ) của một
đồ thị có trọng số là một tập các cạnh chạm tất cả các đỉnh
sao cho tổng trọng số của các cạnh là nhỏ nhất.
Cây bao trùm tối thiểu không nhất thiết là duy nhất trong
một đồ thị.
8 7
b c d
Hình 4.5 Cây bao 9
4
trùm tối thiểu 11
2
14
a
i 4 e
6
8 7 10
h g f
1 2
33
Giải thuật Prim
Giải thuật Prim là giải thuật để giải bài toán cây bao trùm
tối thiểu. Giải thuật này cũng trình bày một chiến lược
để giải một bài toán tối ưu hóa: giải thuật tham lam
(greedy):
Tại mỗi bước của giải thuật, ta phải chọn một trong một
số khả năng lựa chọn. Chiến lược tham lam đề xuất việc
lựa chọn khả năng mà xem ra tốt nhất tại lúc đó.
Một chiến lược như vậy thường không đảm bảo đem lại lời
giải tối ưu toàn cục cho các bài toán.
Tuy nhiên đối với bài toán cây bao trùm tối thiểu, ta có thể
chứng minh rằng chiến lược tham lam có thể đem lại
cây bao trùm với tổng trọng số tối thiểu.
34
Xây dựng cây bao trùm tối thiểu
Có một giải thuật tổng quát mà tạo dựng dần cây bao trùm
tối thiểu một lúc thêm một cạnh.
Giải thuật này duy trì một tập A mà luôn luôn là một tập
con của một cây bao trùm tối thiểu.
Tại mỗi bước, một cạnh (u, v) được chọn để thêm vào tập A
mà không vi phạm hệ thức bất biến A ∪ {(u, v)} luôn luôn là
một tập con của một cây bao trùm tối thiểu.
35
Ta gọi một cạnh như thế là cạnh an toàn cho tập A vì nó có
thể được thêm vào A một cách an toàn mà không vi phạm hệ
thức bất biến nêu trên.
procedure GENERIC_MST(G, w);
/* G is a weighted graph with the weight function w */
begin
A := ∅;
while A does not form a spanning tree do
begin
find an edge (u,v) that is safe for A;
add (u,v) to A.
end
/* the set A at this point is the result */
end;
36
Giải thuật Prim
Trong giải thuật Prim, tập A hình thành một cấu trúc cây. Một
cạnh an toàn được đưa vào A thường là cạnh có trọng số nhỏ
nhất nối cây A với một đỉnh không thuộc về cây.
Cây bao trùm khởi đi từ một nút rễ bất kỳ r và phát triển cho
đến khi cây phủ tất cả mọi đỉnh trong V. Tại mỗi bước, một
cạnh nhẹ (light edge) nối một đỉnh trong A với một cạnh trong
V - A.
Trong quá trình thực hiện giải thuật này, tất cả những đỉnh
mà không thuộc về cây A được đặt trong một hàng đợi có độ
ưu tiên Q dựa trên một trường là key. Với mỗi đỉnh v, key[v] là
cạnh có trọng số nhỏ nhất của những cạnh nối v với một đỉnh
trong cây. Theo qui ước key[v] = ∞ nếu không tồn tại một
cạnh như vậy.
37
Trường p[v] lưu tên của đỉnh cha của đỉnh v trong cây.
procedure MST-PRIM (G, w, r);
/* G is weighted graph with the weight function w, and r is an arbitrary
root vertex */
begin
Q: = V[G];
for each u ∈ Q do key[u]: = ∞;
key[r]: = 0; p[r]: = NIL;
while Q is not empty do
begin
u: = EXTRACT-MIN(Q);
for each v ∈ Q and w(u, v) < key[v] then
/ * update the key field of vertice v */
begin
p[v] := u; key[v]: = w(u, v)
end
end
end;
38
8 7 Hình 4.6. Một thí
4 b c d 8
dụ về giải thuật
2 4
Prim.
11
a i 14 e
7 6
1 2 10
8
h g f
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
39
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
40
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
41
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
42
8 7
4 b c d 8
2 4
11
a i 14 e
7 6
1 2 10
8
h g f
Độ phức tạp của giải thuật Prim tùy thuộc vào cách
mà chúng ta thi công hàng đợi có độ ưu tiên.
43
Độ phức tạp của giải thuật Prim
Tính chất: Nếu Q được thi công như là một heap nhị phân thì
thời gian tính toán của giải thuật Prim là O(E lgV).
Nếu Q được thi công như là một heap nhị phân, chúng ta có
thể lập ra một heap trong bước khởi tạo với chi phí thời gian
là O(V).
Vòng lặp while được thực hiện V lần và vì mỗi thao tác
EXTRACT-MIN có độ phức tạp O(lgV), chi phí tính toán
cho tất cả các lệnh gọi EXTRACT-MIN là O(VlgV).
Vòng lặp for bên trong vòng lặp while được thực hiện tổng
cộng O(E) lần, vì tổng chiều dài của tất cả các danh sách kế
cận là 2E. Việc cập nhật khóa của đỉnh v trong heap tốn
O(lgV) lần. Như vậy, tổng chi phí tính toán của giải thuật
Prim là O(V lgV + 2E lgV) = O(E lgV).
44
3. Đồ thị có hướng
Các đồ thị có hướng là các đồ thị trong đó các cạnh nối với
các nút có hướng.
H I
B C G
D E J K
L M
Hình 4.7. Một thí dụ về đồ thị có hướng
45
Thường thì hướng của các cạnh biểu thị mối liên hệ trước
sau (precedence relationship) trong ứng dụng được mô hình
hóa.
Trong phần này, chúng ta xem xét các giải thuật sau:
- tính bao đóng truyền (transitive closure)
- sắp thứ tự topo (topological sorting)
46
Tính bao đóng truyền
47
Giải thuật Warshall
Có một giải thuật đơn giản để tính bao đóng truyền của
một đồ thị được biểu diễn bằng ma trận kế cận.
for y : = 1 to V do
for x : = 1 to V do
if a[x, y] then
for j: = 1 to V do
if a[y, j] then a[x, j]: = true;
48
Một thí dụ tính bao đóng truyền ( cho đồ thị hình 4.7)
ABCDEFGHI JKLM
A 1 1 0 0 0 11 0 0 00 0 0
B 0 1 0 0 0 00 0 0 00 0 0
C 1 0 1 0 0 00 0 0 00 0 0 Ma trận kế cận ứng
D 0 0 0 1 0 10 0 0 00 0 0 với bước khởi đầu
E 0 0 01 1 00 0 0 0 0 0 0 của giải thuật
F 0 0 0 01 10 0 0 0 0 0 0 Warshall
G 0 0 1 01 01 0 0 1 0 0 0
H 0 0 0 00 01 1 1 0 0 0 0
I 0 0 0 0 0 00 1 1 0 0 0 0
J 0 0 0 0 0 00 0 0 1 1 1 1
K 0 0 0 0 0 00 0 0 0 1 0 0
L 0 0 0 0 0 00 00 0 0 1 1
M 0 0 0 0 0 00 00 0 0 1 1
49
ABCDEFGH I JKLM
A1 1 1 1 1 1 1 0 0 11 1 1
B 01 0 0 0 0 0 0 0 00 0 0 Ma trận kế cận ứng với
C 11 1 1 1 1 1 0 0 11 1 1 bước cuối cùng của giải
D 00 0 1 1 1 0 0 0 00 0 0 thuật Warshall
E 0 0 01 1 1 0 0 0 0 0 0 0
F 0 0 01 1 1 0 0 0 0 0 0 0
G 1 1 111 1 1 0 0 1 1 1 1
Tính chất 5.3.1 Giải
H 1 1 11 11 1 1 1 1 1 1 1
thuật Warshall tính bao
I 1 1 1 1 11 1 1 1 1 1 1 1
đóng truyền với chi phí
J 1 1 1 1 11 1 0 0 1 1 11
O(V3).
K 0 0 0 0 00 0 0 0 01 0 0
L 1 1 11 11 1 0 0 1 1 11
M 1 1 11 11 1 0 0 1 1 11
50
Bài toán các lối đi ngắn nhất
51
Giải thuật Floyd
for y : = 1 to V do
for x : = 1 to V do
if a [x, y] > 0 then
for j: = 1 to V do
if a [y, j] > 0 then
if (a[x, j] = 0) or (a[x, y] + a[y, j] < a [x, j])
then
a[x, j] = a[x, y] + a[y, j];
52
Một thí dụ dùng giải thuật Floyd (cho đồ thi hình 4.7)
ABCD EFGHI JKLM
A 0 1 0 0 0 24 0 0 00 0 0
Ma trận kế cận
B 0 0 0 0 0 00 0 0 00 0 0
ứng với bước
C 1 0 0 0 0 00 0 0 00 0 0
khởi đầu của
D 0 0 0 0 0 10 0 0 00 0 0
giải thuật Floyd
E 0 0 02 0 00 0 0 0 0 0 0
F 0 0 0 02 00 0 0 0 0 0 0
G 0 0 1 01 00 0 0 1 0 0 0
H 0 0 0 00 03 0 1 0 0 0 0
I 0 0 0 0 0 00 1 0 0 0 0 0
J 0 0 0 0 0 00 0 0 0 1 3 2
K 0 0 0 0 0 00 0 0 0 0 0 0
L 0 0 0 0 0 550 0 0 0 0 1
M 0 0 0 0 0 00 00 0 0 1 0
53
ABCDEFGH I JKLM Ma trận kế cận ứng
A 6 1 5 6 4 2 4 0 0 56 8 7 với bước cuối của
B 00 0 0 0 0 0 0 0 00 0 0 giải thuật Floyd
C 12 6 7 5 3 5 0 0 67 9 8
D 0 0 0 5 3 1 0 0 0 00 0 0 Tính chất 5.3.2 Giải
E 0 0 02 5 3 0 0 0 0 0 0 0 thuật Floyd để giải bài
F 0 0 04 2 5 0 0 0 0 0 0 0 toán những lối đi
G 2 3 131 4 6 0 0 1 2 4 3 ngắn nhất giữa những
H 5 6 46 47 3 2 1 4 5 7 6 cặp có độ phức tạp
I 6 7 5 7 5 8 4 1 2 5 6 8 7 tính toán O(V3).
J 10 11 9 11 9 12 8 0 0 9 1 3 2
K 0 0 0 0 0 0 0 0 0 0 0 0 0
L 7 8 68 6 9 5 0 0 6 7 2 1
M 8 9 7 9 7 10 6 0 0 7 8 1 2
54
Giải thích giải thuật Floyd
Giải thuật Floyd lặp V bước trên ma trận kế cận a.
Sau bước lặp thứ y, a[x, j] sẽ chứa chiều dài nhỏ nhất của
bất kỳ lối đi nào từ đỉnh x đến đỉnh j mà không đi qua
những đỉnh mang chỉ số lớn hơn y. Nghĩa là, x và j có thể
bất kỳ đỉnh nào nhưng những đỉnh trung gian trên lối đi
phải nhỏ hơn hay bằng y.
Tại bước lặp thứ y, ta tính các phần tử của ma trận a bằng
công thức sau:
ay[x,j] = min( ay-1[x,j], ay-1[x, y] + ay-1[y, j])
Chỉ số y chỉ trị của một phần tử trong ma trận a sau bước
lặp thứ y.
55
Công thức này được minh họa bằng hình vẽ sau đây.
ay-1[x,y] ay-1[y,j ]
j
x
ay-1[x,j ]
56
Xếp thứ tự tôpô
57
Tập thứ tự riêng phần
Một quan hệ < trên tập S mà thỏa hai tính chất bất đối xứng
và truyền là một quan hệ thứ tự riêng phần (partial ordering)
của S, và một tập có trên nó một quan hệ thứ tự riêng phần
thì được gọi là tập thứ tự riêng phần (partially order set ).
Như vậy, bất kỳ đồ thị có hướng không chu trình có thể được
coi như là một tập thứ tự riêng phần.
58
Tập thứ tự riêng phần (tt.)
Mặt khác, giả sử S là một tập thứ tự riêng phần với quan hệ
thứ tự riêng phần <, thì S có thể được coi như là một đồ thị
có hướng mà các đỉnh là những phần tử trong tập S và các
cạnh được định nghĩa như sau:
(u,v) là một cạnh nếu u < v.
Hơn nữa, ta có thể chứng minh thêm rằng một tập thứ tự
riêng phần S, có thể được coi như là một đồ thị có hướng,
thì không có chu trình.
59
Xếp thứ tự tôpô
60
A
H I
B C G
D E J K
L M
Các nút trong đồ thị ở hình trên có thể được sắp thứ tự tôpô
theo thứ tự sau:
J K L M A G H I F E D B C
61
Sắp thứ tự tôpô1 (Phương pháp 1)
Có vài phương pháp để sắp thứ tự tôpô.
Phương pháp 1
Ý tưởng căn bản là tìm một nút không có nút đi sau (no
successor) loại bỏ nó ra khỏi đồ thị và đưa nó vào một danh
sách.
Lặp lại quá trình này cho đến khi đồ thị rỗng thì sẽ sinh ra một
danh sách. Đảo ngược danh sách này ta sẽ được thứ tự tôpô.
Algorithm:
while the graph has a node with no successors do
remove one such node from the graph and
add it to the end of a list.
if the loop terminates with the graph empty
then the list shows the reserse of a topological order.
else the graph contains a cycle.
62
Hình 4.8 Sắp thứ tự tôpô bằng phương pháp 1
H I
B C G
D E J K
L M
C B D E F I H G A M L K J
J K L M A G H I E F D B C
63
Phương pháp 2 (sắp xếp tôpô)
Phương pháp thứ hai là thực hiện theo kiểu tìm kiếm theo
chiều sâu trước và thêm một nút vào danh sách mỗi khi cần
thiết lấy một nút ra khỏi stack để tiếp tục. Khi gặp một nút
không có nút đi sau thì ta sẽ lấy ra (pop) một phần tử từ
đỉnh stack.
Algorithm:
Start with nodes with no predecessor, put them in the stack.
while the stack is not empty do
if the node at top of the stack has some successors
then
push all its successors onto the stack
else pop it from the stack and add it to the list.
64
1 2
1
0
9
8
3 5
8 6
5 5 5 4 4 4 Hình 4.9 Sắp thứ thự tôpô
3 3 3 3 3 10 10 10 10 bằng phương pháp 2.
2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 9
7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7
65
Bài toán những lối đi ngắn nhất từ một đỉnh nguồn
Thử xét bài toán những lối đi ngắn nhất từ một đỉnh nguồn
(single-source shortest-paths problem):
Cho một đồ thị G = (V, E), chúng ta muốn tìm một lối đi ngắn
nhất từ một đỉnh nguồn nào đó s ∈ V đến mọi nút v ∈ V.
66
Những lối đi ngắn nhất và sự nới lỏng
Giải thuật tìm những lối đi ngắn nhất thường dùng một kỹ thuật
được gọi là sự nới lỏng (relaxation).
Sự nới lỏng là một phương pháp mà liên tiếp giảm cận trên của
trọng số của lối đi ngắn nhất thực sự của mỗi đỉnh cho đến khi
cận trên bằng với trọng số lối đi ngắn nhất.
Như vậy, với mỗi đỉnh v ∈ V, ta duy trì thuộc tính d[v], mà là cận
trên của trọng số của một lối đi ngắn nhất từ đỉnh nguồn s đến v.
Ta gọi d[v] là ước lượng lối đi ngắn nhất (shortest path estimate).
Quá trình nới lỏng một cạnh (u, v) bao gồm việc thử xem ta có
thể cải thiện lối đi ngắn nhất đến v mà đang tìm thấy bằng cách đi
qua u và nếu như vậy, ta cập nhật d[v] và p[v]. Một bước nới lỏng
sẽ làm giảm ước lượng lối đi ngắn nhất d[v] và cập nhật thuộc
tính p[v].
67
2
u 5 9 v
Relax(u,v) 2
u v
u 5 6
v
5 7
2 Relax(u,v)
(a) u v
5 6
2
(b)
68
Giải thuật Dijkstra
Giải thuật này giải bài toán những lối đi ngắn nhất từ đỉnh
nguồn cho một đồ thị có hướng, có trọng số G = (V, E) trong
trường hợp các trọng số của các cạnh là trị không âm.
Giải thuật này duy trì một tập S của các đỉnh mà trọng số của
các lối đi ngắn nhất từ đỉnh nguồn đã được xác định. Nghĩa
là, với mọi đỉnh v ∈ S, ta có
d[v] = min(ước lượng lối đi ngắn nhất từ s đến v)
Giải thuật liên tiến chọn đỉnh u ∈ V – S với thuộc tính d nhỏ
nhất, đưa u vào S, và nới lỏng mọi cạnh đi ra từ u.
Trong giải thuật sau đây, ta dùng hàng đợi có độ ưu tiên Q
chứa tất cả các đỉnh trong V-S, lập khóa theo thuộc tính d. Và
giả định đồ thị G được diễn tả bằng các danh sách kế cận.
69
procedure dijkstra(G, w, s);
/* G is a graph, w is a weight function and s is the source node */
begin
for each vertex v ∈ V[G] do /* initialization */
begin d[v]: = ∞; p[s]: = NIL end;
d[s]: = 0; S: = ∅; Q: = V[G]
while Q is not empty do
begin
u: = EXTRACT-MIN (Q); S: = S ∪ {u};
for each vertex v ∈ Adj [u] do /* relaxation */
if d[v] > d [u] + w(u, v) then
begin d[v]: = d[u] + w(u, v); p[v]: = u end
end
end
70
Giải thuật Dijkstra
• Vì giải thuật Dijkstra luôn luôn chọn đỉnh “gần nhất” trong
V-S để đưa vào tập S, nó thực sự sử dụng chiến lược tham
lam.
• Giải thuật tham lam thường không đảm bảo đem lại lời giải
tối ưu trong trường hợp tổng quát, nhưng đối với bài toán
này, giải thuật Dijkstra thực sự đã đem lại những lối đi ngắn
nhất.
∞ ∞
10 ∞
10 10
0
s 0
s
5 5
∞ ∞ 5 ∞
x y x y
72
u v u v
1
1
8 14 8 13
10
9 10 9
3 3
0 4 s 4
s 2 0 2
7 6
7 6
5
5 7
5 7
2
2
x y x y
73
u v u v
1
8 9 1
8 9
10 9
3 10
6 3 9
2 6
0 0
4 2
7 s 7 4
s 5
5
5 7
5 7
2
2
y x y
x
(s, u): <s, x, u> (s, v): <s, x, u, v>
(s, x): <s, x> (s, y): <s, x, y, y>
74
Độ phức tạp của giải thuật Dijkstra
• Nếu hàng đợi có độ ưu tiên Q = V – S được thực hiện bởi
một mảng tuyến tính, mỗi thao tác EXTRACT-MIN tốn
O(V), và có tất cả |V| thao tác như vậy, do đó ta có một chi
phí tính toán cho thao tác này là O(V2).
• Mỗi đỉnh v ∈ V được đưa vào tập S đúng một lần, do đó mỗi
đỉnh trong các danh sách kế cận Adj[v] được xét đúng một
lần trong suốt tiến trình của giải thuật.
Vì tổng số đỉnh trong tất các các danh sách kế cận là |E|, có
tất cả |E| bước lặp cho vòng lặp for, với mỗi lần lặp tốn O(1).
Thời gian tính toán của giải thuật là
O(V2 + E) = O(V2).
75
Độ phức tạp của giải thuật Dijkstra
• Nếu hàng đợi có độ ưu tiên Q được hiện thực bởi một cấu
trúc heap, mỗi thao tác EXTRACT-MIN tốn chi phí O(lgV),
và có tất cả |V| thao tác này, do đó ta có tổng chi phí cho
thao tác EXTRACT-MIN là O(VlgV).
Phép gán d[v]: = d[u] + w(u, v) đòi hỏi một thao tác cập
nhật khóa của đỉnh v trong heap và nó tốn O(lgV). Có tất cả
|E| thao tác như vậy. Do đó tổng thời gian tính toán của giải
thuật là
O(V lgV + E lgV).
76