II. ĐƯỜNG ĐI NGẮN NHẤT TRONG ĐỒ THỊ CÓ TRỌNG SỐ 1 Các khái niệm
2. Thuật toán tìm đường đi ngắn nhất cho đồ thị có trọng số
2.1 Cơ sở thuật toán tìm đường đi ngắn nhất
Cho G = <X, U> tìm đường đi ngắn nhất từ đỉnh a tới đỉnh b
Với x X nếu độ dài đường đi từ đỉnh xuất phát tới đỉnh x có trọng số là l() thì (x) = l() gọi là trọng số của đỉnh x. Cơ sở của tất cả các thuật toán tìm đường đi ngắn nhất là xác định được các trọng số nhỏ nhất cho tất cả các đỉnh từ đó tìm đường đi ngắn nhất.
Bước 1: Đánh trọng số các đỉnh, trọng số của đỉnh xuất phát là (a) = 0.
Tại các đỉnh còn lại ta ghi một số dương nào đó sao cho nó đủ lớn hơn trọng số của các đỉnh từ a tới.
Bước 2: Thực hiện việc giảm trọng số các đỉnh. Giả sử tại đỉnh x được ghi trọng số (x). Nếu tồn tại đỉnh y có trọng số (y) từ y sang x mà (x) > (y) + l(y, x) thì ta thay trọng số của (x) bởi trọng số '(x) = (y) + l(y, x). Trường hợp (x) < (y) + l(y, x), trọng số của x giữ nguyên là (x). Quá trình thực hiện cho tới khi trọng số của tất cả các đỉnh trong G = <X, U> đạt cực tiểu, tức là x X không tồn tại y X kề với x mà (y) + l(y, x) <(x).
43∑− ∑− = 1 ( ) ) ( n j ij u l l α
Bước 3: Xác định đường từ a đến b có trọng số ít nhất.
Từ bước 3 ta xác định được trọng số của đỉnh b. Xuất phát từ b đi về đỉnh kề với b, chẳng hạn đó là đỉnh xin có tính chất (b) = (x) + l(xin, b). Nếu không có đỉnh kề với xin như vậy thì ta đi về đỉnh kề với b có trọng số cạnh (cung) từ đỉnh đó về b là ít nhất.
Từ đỉnh xin ta đi ngược về đỉnh xin-1 có tính chất (xin) = (in-1) + l(xin-1, xin) nếu không đi về đỉnh kề với xin mà trọng số cạnh (cung) giữa chúng là ít nhất. Bằng cách đó ta sẽ đi về đỉnh xi1 mà đỉnh kề là a sao cho (xi1) = (a) + l(a, xi1) = l(a, xi1) với (a) = 0.
Đường = axi1xi2 ... xin - 1xinb là đường đi từ a đến b có trọng số ít nhất trong số tất cả các đường từ a đến b.
Thật vậy l() = l(a,xi1) + l(xi1,xi2) + ... + l(xin-1,xin) + l(xin,b) = [(xi1)- (a)] + [(xi2) - (xi1)] + ...
+ [(xin) - (xin-1)] + [(b) - (xin)] = (b) (1)
Giả sử D(a, b) là 1 đường bất kỳ từ a đến b và có dạng: = aj1xj2 ... xjk-1xjkb. Theo bước 2 ta có bất đẳng thức sau: l(a, xj1) (xj1) - (a) = (xj1) l(xj1, xj2) (xj2) - (xj1) ... l(xjk-1,xjk) (xjk) - (xjk-1) l(xjk, b) (b) - (xjk) Cộng 2 vế ta có: l() = l(a, xj1) + l(xj1, xj2) + ... + l(xjn-1, xjk) + l(xjk, b) (b) (2) So sánh (1) và (2) ta có: l() = min{ l() / D(a, b)}
2.2 Thuật toán Dijkstra
Có thể khái quát thuật toán bằng thủ tục sau:
Procedure Dijikstra(G: đồ thị liên thông có trọng số dương)}
{G: có các đỉnh a = v0, v1, ..., vn = b và trọng số l(vi, vj) = nếu (vi, vj)
U của G}
For i:=1 to n (vi) := ; (a) := 0;
S :=
{ban đầu các trọng số được khởi tạo sao cho trọng số của a = 0, còn các đỉnh khác bằng, tập S rỗng}
While b S Begin
u:= đỉnh không thuộc S có (u) nhỏ nhất; S := S {u};
For tất cả các đỉnh v không thuộc S
if (u) + l(u,v) < (v) then (v) := (u) + l(u,v)
{thêm vào S đỉnh có trọng số nhỏ nhất và sửa đổi trọng số của các đỉnh không thuộc S}
End;
{l() = l(a, b) = độ dài đường đi ngắn nhất từ a đến b}
Độ phức tạp của thuật toán là O(n2), tức là phải dùng O(n2) phép cộng và so sánh đường đi ngắn nhất giữa 2 đỉnh trong đồ thị đơn vô hướng liên thông có trọng số.
2.3 Thuật toán Ford - Bellman
Khác với thuật toán Dijkstra xác định các trọng số bé nhất của tất cả các đỉnh bằng cách "nổi bọt" dần các trọng số bé nhất, mỗi lần trọng số bé nhất được tìm thấy thì lấy nó làm hạt nhân để điều chỉnh và xác lập các trọng số cho các đỉnh khác, còn thuật toán Ford - Bellman xác định các trọng số nhỏ nhất cho tất cả các đỉnh bằng cách duyệt tất cả các đỉnh, trọng số nhỏ nhất của một đỉnh được xác lập là sau một số lần hữu hạn điều chỉnh nhờ các vòng lặp.
Thuật toán được mô tả bằng thủ tục sau:
Procedure Ford_Bellman;
{Input: Đồ thị có hướng G = <X, U> n đỉnh, a X là đỉnh xuất phát. t(i, j) với i, j X là ma trận trọng số
Output: Với x X tìm các (x) bé nhất và Truoc(x) để ghi nhận đỉnh đi trước x trong các đường đi ngắn nhất từ a đến x.
} Begin {Khởi tạo} For x X do Begin (x) := t[a, x]; Truoc[x] := a; End; (a) := 0; For k:=1 to n - 2 do For x X \ {a} do 45
For y X do
if (x) > (y) + t[y, x] then begin (x) := (y) + t[y, x];
Truoc(x) := y; End;
End;
Độ phức tạp của thuật toán là O(n3) chúng ta có thể chấm dứt vòng lặp theo k khi phát hiện trong quá trình thực hiện 2 vòng lặp trong không có biến d[u] nào bị thay đổi giá trị. Đối với những đồ thị có số cạnh m thoả mãn m < 6.n thì tốt hơn là sử dụng danh sách kề để biểu diễn đồ thị, khi đó vòng lặp theo y cần viết dưới dạng
For tất cả các đỉnh y kề với x do if (x) > (y) + t[y, x] then begin
(x) := (y) + t[y, x]; Truoc(x) := y;
End;
Trong trường hợp này thuật toán có độ phức tạp là O(n.m).
2.4 Thuật toán Floyd.
Trong nhiều trường hợp ta cần xác định đường đi ngắn nhất giữa tất cả các cặp đỉnh, với bài toán này có thể giải bằng cách sử dụng n lần thuật toán thuật toán Ford_bellman trong đó ta sẽ chọn s lần lượt là các đỉnh của đồ thị cách làm này không phải là cách làm tốt nhất ở đây, ta sẽ trình bày thuật toán để giải quyết bài toán này trên đó là thuật toán Floyd. Thuật toán được trình bày khái quát ở dạng thủ tục sau:
Procedure Floyd;
{ Input: đồ thị cho bởi ma trận trọng số a[i,j], i,j = 1,2,...,n; Output: Ma trận đường đi ngắn nhất giữa tất cả các cặp đỉnh. d[i,j], i,j = 1,2,...n. Trong đó d[i,j] cho độ dài ngắn nhất từ i đến j Ma trận ghi nhận đường đi p[i,j] ghi nhận đường đi trước đỉnh j} Begin For i:=1 to n do For j:=1 to n do Begin d[i,j] := a[i,j] p[i,j] := i; End; For k:=1 to n do For i:=1 to n do For j:=1 to n do
if d[i,j] > d[j,k] + d[k,j] then Begin d[i, j] := d[i, k] + d[k,j]; p[i,j] := p[k,j]; End; End; III. CÁC VÍ DỤ ỨNG DỤNG 1. Ứng dụng trong truyền tin
Truyền tin trong mạng thường hay gặp sự cố dẫn đến các gói tin có thể bị sai lệch, để phát hiện và sửa chữa các bít sai khôi phục lại gói tin người ta đã tiến hành 1 số cách thức. Có cách thức đó là mã hoá dữ liệu trước khi truyền, nhờ cơ chế mã hoá mà những gói tin sai có thể sửa đúng lại được. Thực hiện điều này, người ta đã dùng máy hữu hạn trạng thái để mã hoá dữ liệu.
Ta xét 1 ôtômát hữu hạn của máy mã hoá dữ liệu cho những gói tin 4 bít như hình 3.1:
Hình 3.1
Giả sử cần mã hoá gói tin nhị phân x = 0100; Thì ta đi như sau: A, A, C, B, A với nhãn đặt trong trong dấu ngoặc ta thu được gói tin x = 0100 với nhãn đặt ngoài dấu ngoặc tướng ứng thì ta thu được bản mã của x là 00 11 01 11, bản mã này dài 8 bít.
Để tiện minh hoạ, ôtômát thiết kế như trên là đơn giản dẫn đến chưa đầy đủ, vì giả sử có gói tin là x = 1111, ôtômát sẽ không mã hoá được.
Để thực hiện được việc sửa sai gói tin, 1 giải thuật gọi là Viterbi được tiến hành như sau:
- Thay vì bản mã là 8 bít ta xây dựng bản mã có độ dài là 12 bít bẳng cách xuất phát từ trạng thái ban đầu A đi một chu trình có độ dài 6. Khi đó lúc giải mã ta chỉ giải mã 8 bít đầu bỏ đi 4 bit cuối.
- Từ ôtômát xây dựng đồ thị có hướng biểu diễn tất cả các khả năng mã hoá độ dài 12 của gói tin 4 bit như sau:
47A A B C 00(0) 11(0) 00(1) 01(0) 11(1) A B C 1 2 3 4 5 6 0 00 11 01 11 00 00 00 00 00 00 11 11 11 11 11 00 01 01 01 01
Hình 3.2
Mỗi cạnh nét đứt biểu thị số 0, cạnh nét liền là số 1, ví dụ với gói tin x = 0100 để mã hoá 12 bit thì đi theo chu trình trong ôtômát là A, A, C, B, A, A, A thì tương ứng đi trong đồ thị là A0, A1, C2, B3, A4, A5, A6 thì bản mã là 001100110000.
Gọi d(a, b) là số bit sai khác nhau giữa 2 chuỗi nhị phân a và b cùng độ dài, nếu x, y, z là những chuỗi bít nhị phân cùng độ dài n, thì thoả các tính chất sau:
i) d(x, y) 0
ii) d(x, y) = 0 x = y iii) d(x, y) + d(y, z) d(x, z)
i) và ii) hiển nhiên bây giờ ta chứng minh iii)
Đặt x = x1, x2, ..., xn với xi (i = 1..n) là những bit, xi = 0 hoặc 1 tương tự đặt y = y1, y2, ..., yn và z = z1, z2, ..., zn
a) Xét trường hợp n = 1, tức các chuỗi chỉ có 1 bit ta có - Nếu d(x1, y1) = 1 và d(y1, z1) = 1 thì d(x1, z1) = 0 hoặc 1 dẫn đến d(x1, y1) + d(y1, z1) = 2 luôn lớn d(x1, z1)
- Nếu d(x1, y1) = 1 và d(y1, z1) = 0 thì y1 = z1 nên d(x1, z1) = 1 hoặc d(x1, y1) = 0 và d(y1, z1) = 1 thì x1 = y1 nên d(x1, z1) = 1 hoặc d(x1, y1) = 0 và d(y1, z1) = 0 thì x1 = y1 = z1 nên d(x1, z1) = 0 tất cả các trường hợp đều có d(x1, y1) + d(y1, z1) = d(x1, z1)
Kết luận d(xi, yi) + d(yi, zi) d(xi, zi) với i = 1..n b) Xét trường hợp n > 1
Ta thấy d(x, y) = d(x1, y1) + d(x2, y2) + ... + d(xn, yn) d(y, z) = d(y1, z1) + d(y2, z2) + ... + d(yn, zn) d(x, z) = d(x1, z1) + d(x2, z2) + ... + d(xn, zn)
mà d(xi, yi) + d(yi, zi) d(xi, zi) với i = 1.. n ta cắt lát theo chiều dọc suy điều phải chứng minh.
Nhận xét rằng ôtômát như hình 3.1 được thiết kế với mục đích chỉ sửa sai với những bản mã có sai sót không quá 2 bit. Giả sử a, b là 2 bản mã 12 bit bất kỳ của ôtômát, ôtômát cũng được thiết kế sao cho d(a, b) 5 (ta có thể kiểm chứng điều này).
Giả sử x là bản mã 12 bít do ôtômát tạo ra nhưng khi truyền đi thì bị sai sót không quá 2 bit, cách sửa đúng lại x như sau:
Gọi U là tập các chuỗi mã 12 bit ra ôtômát tạo ra khi đó a U là bản mã gốc của x nếu d(x, a) = min{d(x, b) /b U}
khi đó a là duy nhất, không thể tồn tại mã b sao cho d(a, x) = d(x, b) vì theo trên
d(a, b) 5 và d(a, x) + d(x, b) d(a, b) mà d(a, x) 2 suy ra d(x, b) > 2. Như vậy đường đi từ đỉnh A0 đến A6 tương ứng với đoạn mã a trong đồ thị hình 3.2 là đường đi có độ dài nhỏ nhất, mà trọng số của mỗi cung là số bit sai khác với từng cặp 2 bit trong x, để rõ hơn ta xét ví dụ:
Giả sử nhận được bản tin bị sai không quá 2 bit x = 10 01 00 01 11 10 khi đó ta đánh trọng số cho mỗi cung cho đồ thị như hình 3.3
Hình 3.3
Cách đánh trọng số mỗi cung như sau, ở cặp 2 bit thứ nhất của x là 10 thì tương ứng với những cung thứ nhất của tất cả đường đi là A0A1 = 00; A0C1 = 11 và có số bit sai khác lần lượt là 1, 1, đây cũng là trọng số của cung tương ứng. Tương tự ở cặp 2 bit thứ hai của x là 01 thì tương ứng những cung thứ hai của tất cả đường đi là A1A2 = 00; A1C2 =11; C1B2 = 01 và có số bít sai khác lần lượt là 1, 1, 0. Tương tự ta tiếp tục đánh trọng số như thế...
Nhận thấy đường đi ngắn nhất của đồ thị hình 3.3 là A0C1B2C3B4A5A6 Vậy đoạn mã tương ứng là 11 01 00 01 11 00, đây cũng là đoạn mã đúng của x và giải mã ta thu được gói tin được truyền là 1010.