7.6.1. Mở đầu
Nhiều bài toán có thể mô hình hóa bằng đồ thị có trọng số. Đó là đồ thị mà mỗi cạnh của nó đƣợc gán một con số (nguyên hoặc thực) gọi là trọng số ứng với cạnh đó. Ví dụ ta cần mô hình một hệ thống đƣờng hàng không. Trong mô hình cơ sở trƣớc đây, mỗi thành phố đƣợc biểu diễn bằng một đỉnh, mỗi chuyến bay là một cạnh nối hai đỉnh tƣơng ứng. Nếu trong bài toán đang xét ta cần tính đến khoảng cách giữa các thành phố thì ta cần gán cho mỗi cạnh của đồ thị cơ sở một con số tƣơng ứng với khoảng cách giữa hai thành phố tƣơng ứng. Nếu quan tâm đến thời gian của mỗi chuyến bay thì ta lại gán các thời lƣợng này cho mỗi cạnh của đồ thị cơ sở. Nếu trong bài toán đang xét ta quan tâm đến tiền vé của mỗi chuyến bay thì ta lại gán giá vé nhƣ trọng số cho mỗi cạnh.
Đồ thị có trọng số cũng có thể đƣợc dùng để lập mô hình các mạng máy tính. Chi phí truyền thông (chẳng hạn tiền thuê bao đƣờng điện thoại hàng tháng), thời gian đáp ứng của các máy tính qua các đƣờng truyền thông này, hoặc là khoảng cách giữa các máy tính, tất cả đều có thể đƣợc nghiên cứu bằng cách dùng một đồ thị có trọng số.
Trong các ứng dụng chúng ta gặp nhiều loại bài toán liên quan đến đồ thị có trọng số. Chẳng hạn xác định đƣờng đi ngắn nhất nối giữa hai đỉnh trong mạng, trong đó ta coi độ dài của
37 đồ thị không trọng số, đƣờng đi đƣợc hiểu là số cạnh có trên đƣờng đi. Trong trƣờng hợp này ta cũng có thể xem trọng số của mỗi cạnh là 1, và việc tìm đƣờng đi ngắn nhất tƣơng đƣơng với việc tìm đƣờng đi có ít cạnh nhất. Một cách tự nhiên, nếu 2 đỉnh không có đƣờng đi thì ta nên gán cho khoảng cách giữa chúng là , có nghĩa là muôn trùng xa cách, chứ ta không thể gán cho khoảng cách giữa chúng là 0 đƣợc). Dĩ nhiên bài toán tìm đƣờng đi ngắn nhất giữa hai đỉnh a và b chỉ có nghĩa khi thực sự có đƣờng đi giữa a và b. Vậy ở đây ta sẽ giả thiết đồ thị đang khảo sát là đơn đồ thị liên thông.
7.6.2. Thuật toán Dijkstra xác định các đường đi ngắn nhất từ một đỉnh đến các đỉnh còn lại
Có một số thuật toán tìm đƣờng đi ngắn nhất giữa hai đỉnh của một đồ thị. Có lẽ thuật toán tự nhiên nhất là ta tính tất cả các con đƣờng có thể có từ đỉnh u đến đỉnh v, sau đó ta chọn đƣờng đi có độ dài bé nhất. tuy nhiên khi số đỉnh lớn thì việc liệt kê tất cả các con đƣờng là việc làm khó có thể thực hiện. Có một số thuật toán khả thi hơn đã đƣợc đề xuất.
Giả sử ta xét đồ thị vô hƣớng G = (V,E) , trong đó tập hợp các cạnh đƣợc cho bởi V = {v0, v1,..., vn}. Ta ký hiệu trọng số của cạnh giữa hai đỉnh vi và vj là [vi,vj] hoặc wij, đây là khoảng cách trực tiếp giữa 2 đỉnh không thông qua đỉnh trung gian. Rõ ràng đƣờng đi trực tiếp từ một đỉnh đến chính nó phải đƣợc xem là bằng 0 vì nó đã ở chính nó và đƣờng đi trực tiếp từ một đỉnh tới một đỉnh khác mà không có cạnh nối chúng không thể xem là một số hữu hạn, nghĩa là phải xem đƣờng đi đó là vô cùng lớn. Vậy ta đặt wii = 0 và wij = .
Có lẽ ý tƣởng đầu tiên nảy sinh khi ta gặp bài toán tìm đƣờng đi ngắn nhất giữa hai đỉnh là liệt kê tất cả các đƣờng đi có thể có giữa hai đỉnh này và chọn trong đó đƣờng đi có độ dài ngắn nhất. Tuy nhiên ta sẽ gặp phải một số trở ngại khi có quá nhiều đỉnh trong đồ thị. Thật vậy ta phải liệt kê tất cả các đƣờng đi độ dài 1, tức là đƣờng đi trực tiếp, độ dài 2, độ dài 3, ... ,độ dài n-1 (Giả thiết rằng đồ thị có n đỉnh). Số các đƣờng đi cần xét có thể nhƣ sau:
Độ dài 1: 1 Độ dài 2: n-2
Độ dài 3: (n-2)(n-3) Nếu là đồ thị vô hƣớng thì là (n-2)(n-3)/2 ...
Độ dài k: (n-2)(n-2)...(n-k) ...
Độ dài n-1: (n-2)(n-2)...1 = (n-2)!
Rõ ràng đây là bài toán có độ phức tạp n!, nói chung không thể giải đƣợc với n lớn.
Có một số thuật toán khả thi hơn đã đƣợc đề xuất. Chúng ta sẽ tìm hiểu ở đây thuật toán do Dijkstra, nhà toán học ngƣời Hà lan, đề xuất năm 1959. Phiên bản mà ta sẽ trình bày sẽ đƣợc giả thiết là đồ thị đơn có hƣớng, các trọng số dƣơng.
Giả sử ta xét đồ thị có hƣớng G = (V,E) , trong đó tập hợp các cạnh đƣợc cho bởi V = {v0, v1,..., vn}. Ta ký hiệu trọng số của cung giữa hai đỉnh vi và vj là [vi,vj] hoặc wij, đây là khoảng cách trực tiếp giữa 2 đỉnh không thông qua đỉnh trung gian. Rõ ràng đƣờng đi trực tiếp từ một đỉnh đến chính nó phải đƣợc xem là bằng 0 vì nó đã ở chính nó và đƣờng đi trực
tiếp từ một đỉnh tới một đỉnh khác mà không có cạnh nối chúng không thể xem là một số hữu hạn, nghĩa là phải xem đƣờng đi đó là vô cùng lớn. Vậy ta đặt wii = 0 và wij = nếu không có cạnh nối hai đỉnh vi và vj .
Những ý tƣởng dẫn đến thuật toán Dijkstra
Giả sử ta cần tìm đƣờng đi ngắn nhất từ đỉnh a đến đỉnh b. Thay vì tìm chỉ một khoảng cách bé nhất từ a đến b, ta tìm một số khoảng cách bé nhất từ a đến các đỉnh khác trong đó có b. Nghĩa là lời giải của ta rộng hơn nhiều so với mục tiêu đặt ra. Điều này nhìn qua có vẻ nhƣ là sự lãng phí. Nhƣng trong thực tế, cách làm này vẫn còn hiệu quả hơn nhiều so với cách liệt kê tất cả các con đƣờng từ a đến b.
Ta ký hiệu (u,v) là đƣờng đi ngắn nhất từ đỉnh u đến đỉnh v và D(u,v) là độ dài đƣờng đi này. Trong trƣờng hợp đỉnh a cố định, ta ký hiệu đơn giản là (x) và D(x) để ký hiệu đƣờng đi ngắn nhất từ a đến x và độ dài của nó.
Ta sẽ sắp xếp tập các đỉnh {v1,v2,...,vn} thành dãy x0, x1,..., xn-1 thỏa mãn tính chất sau đây: D(x0) D(x1) ... D(xk) ... D(xn-1) (8.1) Ta sẽ xây dựng dần tập Sk V trong đó
Sk = {x0, x1,..., xk }
Nhƣ ta sẽ thấy, việc xác định đƣờng đi ngắn nhất giữa đỉnh a và b là việc không thể thực hiện đƣợc ngay bằng một số phép toán đơn giản. Tuy nhiên nếu chúng ta lần lƣợt xác định các xi để thỏa mãn (8.1) thì công việc lại dễ dàng hơn. Lý do là vì tập Sk có thể đƣợc xác định không mấy khó khăn nếu dựa vào Sk-1. Mà S0 là tập xuất phát lại có thể xác định đƣợc ngay, đó chính là phần tử a.
Rõ ràng trong tập V thì phần tử có khoảng cách ngắn nhất đến a là chính nó, khoảng cách này nhƣ ta đã giả thiết ở trên, là bằng 0. Vậy S0 = {a }.
Sau đó tập S sẽ đƣợc bổ sung dần từng phần tử xi và sẽ dừng lại khi gặp phần tử b, tức là dừng lại ngay khi b đƣợc đƣa vào tập hợp Sk .
Nhƣ chúng ta sẽ thấy trong ví dụ sau, đƣờng đi ngắn nhất từ a đến b nói chung không chứa tất cả các đỉnh x1, x2,...,xk = b mà chỉ và chỉ chứa các đỉnh thuộc tập hợp này. Nếu một đƣờng đi là ngắn nhất thì mọi phần của nó cũng là ngắn nhất. Điều này có nghĩa là nếu có 2 đỉnh u và v nằm trên đƣờng đi ngắn nhất từ a đến b và u đứng trƣớc v thì phần đoạn đƣờng từ u đến v của đƣờng đi ngắn nhất (a,b) là đƣờng đi ngắn nhất từ u đến v. Vì vậy nếu cố định đỉnh xuất phát và dùng một P(x) để chỉ nút đứng trƣớc đỉnh x trong đƣờng đi ngắn nhất từ a đến x thì từ một đỉnh x bất kỳ ta có thể lần ngƣợc lại để chỉ ra đƣờng đi ngắn nhất.
Phƣơng pháp chúng ta sử dụng đƣợc gọi là phƣơng pháp "tham lam" (greedy). Thay vì chỉ tìm đƣờng đi ngắn nhất từ a đến b ta lại tìm đƣờng đi ngắn nhất từ a đến tất cả các đỉnh có khoảng cách ngắn nhất đến a nhỏ hơn khoảng cách ngắn nhất từ a đến b.
Trƣớc hết chúng ta xét một ví dụ cụ thể để xem thuật toán Dijkstra đƣợc thực hiện nhƣ thế nào.
39 Giả sử ta có đồ thị sau:
Giả sử ta cần tìm đƣờng đi ngắn nhất từ đỉnh A đến đỉnh H.
Tập S0 ban đầu chỉ gồm một đỉnh A. Đỉnh x1 tiếp theo đƣợc chọn là đỉnh B, rồi đến đỉnh C, đỉnh D, sau đó là đỉnh E, đỉnh F, G, H. Vậy dãy các đỉnh đƣợc xác định là:
A,B,C,D,E,F,G,H.
Nhƣ ta thấy, dãy các đỉnh này không nhất thiết tạo thành đƣờng đi. Nhƣng đƣờng đi ngắn nhất từ A đến H phải có các đỉnh trong là các đỉnh thuộc tập Sk.
Sau đây chúng ta sẽ khảo sát một số tính chất của các tập Sk và dựa vào các tính chất đó để đƣa ra thuật toán xây dựng tập này.
Bước 0: Nhƣ ta đã thấy, ban đầu ta có S0 = {a }.
Bước 1: Tiếp theo ta sẽ chọn x1 sao cho khoảng cách ngắn nhất từ nó đến a là bé nhất so với các đỉnh khác. Ta thấy rằng đƣờng đi ngắn nhất từ a đến x1 phải là đƣờng đi trực tiếp. Vì giả sử lại có một đỉnh trong u trên đƣờng đi này, ví dụ đƣờng đi là (a,u,x1) thì ta thấy rằng khoảng cách từ a đến u còn bé hơn là khoảng cách bé nhất từ a đến x1, nghĩa là x1 không phải là phần tử có khoảng cách đến a là bé nhất nhƣ đã giả thiết. Vậy độ dài sử dụng để tìm x1 chính là khoảng cách trực tiếp từ đỉnh a đến các đỉnh còn lại. Đỉnh x1 đƣợc chọn nếu khoảng cách trực tiếp từ a đến nó là bé nhất. Đặt S0 = {a}, D0(y) = w(a,y), ta thấy rằng D(a) = D0(a) = 0. Theo yêu cầu đặt ra thì x1 đƣợc chọn phải thỏa mãn:
D(x1) = min{D(y) | y V \ S0} (8.2)
Tuy nhiên nhƣ ta đã thấy, tiêu chuẩn (8.2) tƣơng đƣơng với tiêu chuẩn D0(x1) = min{w(a,y) | y V \ S0}= min{D0(y) | y V \ S0} (8.3)
Sau bƣớc này, đƣờng đi ngắn nhất từ a đến x1 đã đƣợc xác định. Đó là đƣờng đi (a,x1) và khoảng cách ngắn nhất D(x1) cũng đƣợc xác định là D(x1) = w(a,x1). Để ghi lại đƣờng đi ngắn nhất, ta dùng mảng P[i] để chỉ đỉnh đứng trƣớc i trong đƣờng đi ngắn nhất từ a đến đỉnh i. Theo cảm giác, ta cần một mảng hai chiều để lƣu lại đƣờng đi cho mỗi đỉnh. Tuy nhiên nhƣ ta sẽ thấy, do tính chất đặc biệt của đƣờng đi ngắn nhất, ta chỉ cần dùng mảng một chiều. Nhƣ vậy ta có P[x1] = a.
1 A E F G H B D C 1 1 3 4 1 5 1 S u x a
Bước 2: Đặt S1 = {a,x1}. Bây giờ ta sẽ xác định đỉnh x2 V\ S1 sao cho D(x2) là bé nhất trong tập các đỉnh còn lại. Nghĩa là
D(x2) = min{D(y) | y V \ S1} (8.4)
Có thể thấy rằng đƣờng đi ngắn nhất từ đỉnh a đến x2 hoặc là trực tiếp, hoặc là chỉ qua x1. Thật vậy, giả sử đƣờng đi ngắn nhất từ a đến x2 lại đi qua một đỉnh u nào đó , tức là a,...,u,...,x2 thì rõ ràng đƣờng đi từ a đến u trên con đƣờng này ngắn hơn đƣờng từ a đến x2, nghĩa là đáng lẽ ra u phải đƣợc chọn chứ không phải là x2.
Trong tập các đỉnh thuộc V\S1 ta định nghĩa độ dài đặc biệt từ a đến một đỉnh y là:
D1(y) = min(w(a,y), w(a,x1)+w(x1,y)) (8.5) Với định nghĩa này, ta cần xác định x2 sao cho
D1(x2) = min{D1(y) | y V \ S1} (8.6)
Ta chứng minh rằng nếu x2 thỏa (8.6) thì cũng thỏa (8.4) và D(x2) = D1(x2). Thật vậy giả sử x2 thỏa (8.6) nhƣng không thỏa (8.4). Khi đó tồn tại x x2 và thỏa mãn (8.4). Nhƣ trên đã chỉ ra, đƣờngđi ngắn nhất từ a đến x không thể đi qua một đỉnh khác ngoài đỉnh x1, vì nếu đi qua một đỉnh u nào đó thì chính đỉnh u mới là đƣợc chọn. Do đó đƣờng đi ngắn nhất từ a đến x D(x) là giá trị nhỏ hơn trong hai giá trị w(a,y) và w(a,x1)+w(x1,y), tức là
D(x) = min(w(a,x), w(a,x1)+w(x1,x)) Nhƣng theo (8.5) thì
D(x) D1(x2) D(x2)
Điều này mâu thuẫn với điều đã giả thiết.
Bước 3: Đặt S2 = {a,x1,x2}. Tiếp theo ta sẽ xác định đỉnh x3 sao cho D(x3) là bé nhất trong tập các đỉnh còn lại. Nghĩa là
D(x3) = min{D(y) | y V \ S2} (8.7)
Ta thấy rằng đƣờng đi ngắn nhất từ a đến x3 không thể chứa đỉnh trung gian z V\S2, mà chỉ có thể chứa các đỉnh thuộc tập S2, vì nếu điều này xảy ra thì z đƣợc chọn đƣa vào tập S2 (để tạo thành S3) chứ không phải là x3. Ta thấy rằng đƣờng đi ngắn nhất (x3) có thể chứa x2 hoặc không. Nếu nó không chứa x2 thì đây chính là đƣờng đi đặc biệt tại bƣớc trƣớc từ a đến x3, hay là D1(x3). Còn nếu nó đi qua x2 thì chính là đƣờng đi đặc biệt từ a đến x2, rồi đi trực tiếp từ x2 đến x3 (mặc dầu từ x2 đến x3 có thể có đƣờng ngắn hơn đƣờng trực tiếp, nhƣng nhƣ ta thấy, đƣờng đi này không có đỉnh trung gian ngoài S2. Vậy khoảng cách D(x3) là
41 Nếu ta định nghĩa khoảng cách D2(y) cho mọi phần tử V\S2 nhƣ sau:
D2(y) = min(D1(y),D1(x2)+w(x2,y))
ở bƣớc này nếu D2(x3) là D1(x3) thì P(x3) đƣợc giữ nguyên nhƣ bƣớc trƣớc, còn nếu là D1(x2)+w(x2,x3) thì ta đặt P(x3) = x2.
Thì phần tử x3 phải thỏa mãn:
D2(x3) = min{D2(y) | y V \ S2} (8.8)
Bằng quy nạp toán học ta có thể chứng minh tính đúng đắn của trƣờng hợp tổng quát xác định xk+1 tại bƣớc k nhƣ sau:
Ta định nghĩa khoảng cách Dk(y) cho mọi phần tử V\Sk : Dk(y) = min(Dk-1(y),Dk-1(xk)+w(xk,y))
Tại bƣớc này nếu Dk(y) là Dk-1(y) thì P(y) đƣợc giữ nguyên nhƣ bƣớc trƣớc, còn nếu là Dk-1(y)+w(xk,y) thì ta đặt P(y) = xk.
Thì phần tử xk+1 phải thỏa mãn:
Dk(xk+1) = min{Dk(y) | y V \ Sk} (8.9)
Từ đây ta suy ra rằng, để chọn x bổ sung vào S, ta chỉ cần xét các đƣờng đi đặc biệt từ a tới yV\S, đó là đƣờng đi ngắn nhất trong các đƣờng đi từ a đến y không qua các đỉnh
trung gian ngoài S. Ta chú ý rằng chỉ khi x đƣợc chọn bổ sung vào S thì đƣờng đi đặc biệt
tới x mới đƣợc cố định và là đƣờng đi ngắn nhất cần tìm. Còn nếu đỉnh y chƣa đƣợc chọn thì đƣờng đi đặc biệt tƣơng ứng với nó còn có thể thay đổi từ bƣớc này sang bƣớc khác. Mỗi lần có một đỉnh mới đƣợc bổ sung vào S thì đƣờng đi đặc biệt đƣợc tính lại và thƣờng bé hơn bƣớc trƣớc đó. Nếu tại bƣớc h y đƣợc chọn thì ta có:
D0(y) D1(y) ... Dh(y) = Dh+1(y) = ... = D(y)
Tại mỗi bƣớc đƣờng đi đặc biệt tới một đỉnh có thể không duy nhất (trƣờng hợp có nhiều hơn một đƣòng đặc biệt có cùng độ dài), nhƣng ta có thể chọn một đƣờng thỏa mãn điều kiện và nhƣ vậy đƣờng đi đặc biệt tới một đỉnh luôn luôn xác định.
Rõ ràng nếu x đƣợc chọn để bổ sung vào S thì đƣờng đi ngắn nhất từ a đến nó đã đƣợc xác định
D(x) cho độ dài ngắn nhất chứ không cho biết đƣờng đi đó nhƣ thế nào. Để ghi nhận đƣờng đi ngắn nhất ta đã định nghĩa một hàm P từ tập V đến tập V theo cách P(x)=u, trong đó u là đỉnh đứng ngay trƣớc x trên đƣờng ngắn nhất từ a đến x. Vì một đƣờng đi ngắn nhất chỉ chứa các đƣờng đi ngắn nhất, do đó ta có P(x) là nút đứng trƣớc x, P(P(x)) lại là nút đứng trƣớc P(x),...Cứ nhƣ vậy nếu ta viết lùi dần cho đến khi gặp a sẽ đƣợc đƣờng đi ngắn nhất đến x.
a, ..., P(P(P(x))), P(P(x)), P(x), x
Những điều trên đây có thể hệ thống lại trong thuật toán sau:
Bƣớc 0: Đặt S0 = {a}; D(a) = D0(a) = 0; D0(y) = w[a,y] , P0(y) = a, với y a. Tìm x1 sao cho D0(x1) = min{D0(y) | y V\S0}. Ban đầu ta chỉ xét đƣờng đi trực tiếp từ a
đến các đỉnh khác, do đó đỉnh đứng trƣớc đỉnh y chính là a, cho dù đƣờng đi này bằng .
Bƣớc 1: Đặt D(x1) = D0(x1); S1 = {a, x1}. Tính D1(y) với yV \ S1 . Đƣờng đi đặc biệt tới đỉnh y sẽ là đƣờng ngắn hơn trong hai đƣờng: đƣờng đi đặc biệt không qua đỉnh x1 mới bổ sung hoặc qua đỉnh x1 .
Vậy D1(y) = D1(a,y) = min {D0(a,y), D0(a,x1) +w(x1,y)}
Nhƣ vậy khi tính lại nhãn D1(y) ta chỉ cần so sánh giá trị cũ của nó là D0(y) với giá trị D0(a,x1) + w(x1,y) và chọn giá trị nhỏ hơn.
Ta cần tính P1(y) theo công thức:
P1(y) = ) , ( ) ( ) ( ) ( ) ( ) ( 1 1 0 1 1 0 1 0 y x w x D y D khi x y D y D khi y P
Xác định x2 sao cho D1(x2) = min {D1(y) | y V\S1}
Bƣớc 2: Đặt D(x2) = D1(x2); S2 = {a, x1,x2}. Tính D2(y) với yV \ S2 . Đƣờng đi đặc biệt tới đỉnh y sẽ là đƣờng ngắn hơn trong hai đƣờng: đƣờng đi đặc biệt không qua