Thuật toán Dijkstra

Một phần của tài liệu Một số vấn đề về mạng lưới trong cơ sở dữ liệu không gian (Trang 33)

Thuật toán này do nhà khoa học Hà Lan - ông Edsger Dijkstra đưa ra vào năm 1959 cung cấp nền tảng cơ bản cho thuật toán hữu hiệu nhất để giải quyết bài toán này. Thuật toán Dijkstra được thiết kế dựa trên kỹ thuật tham ăn để giải quyết bài toán tìm đường đi ngắn nhất từ một đỉnh nguồn trong đồ thị có hướng với trọng số không âm. Thuật toán Dijkstra hoạt động dựa trên việc gán nhãn cho các đỉnh [7].

a- Phát biu bài toán

Đầu vào: Cho một đồ thị có hướng G = (V, E) (với V: tập đỉnh, E: tập cạnh), một hàm trọng sốw: E→ [0, ∞) và một đỉnh nguồn s.

Đầu ra: Hãy tìm đường đi ngắn nhất từ nguồn sđến mỗi đỉnh trong đồ thị.

b- Ý tưởng chính:

Giả sử đã biết k đỉnh gần đỉnh s nhất về tổng chiều dài trong đồ thị và

đường đi ngắn nhất từ s đến mỗi đỉnh đó. Gán nhãn đỉnh s và k đỉnh này với khoảng cách ngắn nhất từ s. Sau đó, đỉnh thứ k+1 gần đỉnh x nhất được tìm thấy như sau. Với mỗi đỉnh được gán nhãn y, xây dựng k đường đi phân biệt từ s đến y bằng cách kết nối đường đi ngắn nhất từ s đến x với cung (x, y) cho tất cả các

đỉnh x đã được gán nhãn. Chọn đường đi ngắn nhất trong k đường đi này và đặt nó là đường đi ngắn nhất từ s đến y.

Vì vậy, nếu đã biết đỉnh thứ k gần đỉnh s nhất thì đỉnh thứ k+1 có thể được xác định như trên. Bắt đầu với k=0, quá trình này có thể lặp đi lặp lại cho

đến khi tìm ra đường đi ngắn nhất từ s đến t (tức là t đã được gán nhãn).

c- Thut toán

• Thuật toán Dijkstra được thực hiện như sau:

Bước 1:

- Ban đầu, tất cả các cung và các đỉnh chưa được gán nhãn.

- Với mỗi đỉnh x ∈ V, gọi d(x) là nhãn được gán cho mỗi đỉnh x là độ dài đường đi ngắn nhất từ s đến x. - Khởi tạo, đặt d(s) = 0 và d(x) = ∞ cho mọi đỉnh x ≠ s. - Gọi y là đỉnh cuối cùng được gán nhãn. Gán nhãn đỉnh s và đặt y = s. Bước 2:

- Với mỗi đỉnh chưa được gán nhãn x, tính lại nhãn d(x) như sau: d(x) = min{d(x), d(y) + a(x, y)} (2.1)

- Nếu d(x) = ∞ với mọi đỉnh x chưa gán nhãn thì dừng lại vì không tồn tại đường đi từ s đến đỉnh bất kỳ chưa gán nhãn.

- Ngược lại,

+ Gán nhãn cho đỉnh x chưa gán nhãn với giá trị d(x) nhỏ nhất.

+ Gán nhãn cho cung có hướng đến đỉnh x từ đỉnh đã được gán nhãn với giá trị d(x) ở trên.

+ Đặt y = x.

Bước 3:

- Nếu đỉnh t đã được gán nhãn thì dừng, vì đường đi ngắn nhất từ s đến t

đã được tìm ra. Đường đi này chứa đường đi đơn của các cung đã được gán nhãn từ s đến t.

- Nếu đỉnh t chưa được gán nhãn thì lặp lại bước 2.

Chú ý rằng bất cứ khi nào thuật toán gán nhãn cho đỉnh (ngoại trừ đỉnh s) thì cũng gán nhãn cho cung hướng về đỉnh này. Vì vậy, mỗi đỉnh có tối đa một cung đã được gán nhãn hướng vào nó và các cung đã được gán nhãn không có chu trình vì không có cung nào được gán nhãn nếu cả hai điểm đầu cuối có cung

đã được gán nhãn vốn gắn liền với nó. Do đó, chúng ta có thể kết luận rằng các cung đã được gán nhãn tạo thành một dạng cây có gốc tại s. Dạng cây này được gọi là cây đường đi ngắn nhất. Đường đi duy nhất từ s đến bất kỳ điểm x nào khác nằm trong cây đường đi ngắn nhất là đường đi ngắn nhất từ s đến x.

Nếu đường đi ngắn nhất từ s đến x trong cây đường đi ngắn nhất đi qua

đỉnh y thì phần đường đi từ y đến x này là đường đi ngắn nhất từ y đến x. Ngược lại thì tồn tại đường đi khác, thậm chí còn ngắn hơn đường đi từ y đến x, điều này mâu thuẫn với đường đi ngắn nhất từ s đến x đã tìm ra.

Vì các cung đã được gán nhãn luôn luôn tạo thành cây nên có thể xem thuật toán này như phát triển một cây có gốc tại đỉnh s. Một khi đến được đỉnh t thì có thể kết thúc quá trình phát triển.

M rng thut toán:

Nếu muốn tìm đường đi ngắn nhất từ đỉnh s đến mọi đỉnh khác trong đồ

thị thì tiếp tục quá trình phát triển cho đến khi tất cả các đỉnh được nằm trong cây đường đi ngắn nhất. Trong trường hợp này, cây trở thành cây mở rộng (nếu

cây tồn tại). Khi đó, bước 3 sẽ là:

Bước 3:

- Nếu tất cả các đỉnh đã được gán nhãn thì dừng lại vì đường đi duy nhất của các cung đã được gán nhãn từ s đến x là đường đi ngắn nhất từ s

đến x với mọi đỉnh x. - Ngược lại, trở về bước 2.

d- Ví d minh ha

Tìm đường đi ngắn nhất từ nút s đến nút t trong đồ thị G trong hình 2.1

sau bằng thuật toán Dijkstra:

2 4 3 3 2 2 7 s 1

Hình 2.1 Đồ th minh ha thut toán Dijkstra

Bước 1: Khởi tạo, d(s) = 0, d(x) = ∞ với mọi x ≠ s. Đặt y = s.

Bước 2: Tính lại khoảng cách cho các nút chưa gán nhãn: d(1) = min{d(1), d(s) + a(s, 1)} = min{∞, 0 + 4} = 4 d(2) = min{d(2), d(s) + a(s, 2)} = min{∞, 0 + 7} = 7 d(3) = min{d(3), d(s) + a(s, 3)} = min{∞, 0 + 3} = 3

Vì khoảng cách nhỏ nhất trong các nút chưa gán nhãn là d(3) = 3 nên gán nhãn cho nút 3 và cung (s, 3). Cây đường đi ngắn nhất hiện tại chứa cung (s, 3).

Đặt y = 3.

Bước 3: Nút t chưa được gán nhãn nên quay lại bước 2.

Bước 2: Tính d(4) = min{d(4), d(3) + a(3, 4)} = min{∞, 3 + 3} = 6

Khoảng cách nhỏ nhất trong các nút chưa gán nhãn là d(1) = 4 nên gán nhãn cho nút 1 và cung (s, 1). Cây đường đi ngắn nhất hiện tại chứa cung (s, 3) và (s, 1). Đặt y = 1.

Bước 3: Đỉnh t chưa được gán nhãn nên quay lại bước 2.

Bước 2: Tính d(2) = min{d(2), d(1) + a(1, 2)} = min{7, 4 + 3} = 7

2

t

4 3

d(4) = min{d(4), d(1) + a(1, 4)} = min{6, 4 + 2} = 6

Khoảng cách nhỏ nhất trong các nút chưa gán nhãn là d(4) = 6 nên gán nhãn cho nút 4 và cung (1, 4) hoặc (3, 4) vì cả hai đều bằng d(4).

Chọn tùy ý cung (3, 4). Khi đó, cây đường đi ngắn nhất chứa cung (s, 3), (s, 1) và (3, 4). Đặt y = 4.

Bước 3: Đỉnh t chưa được gán nhãn nên quay lại bước 2.

Bước 2: Tính d(t) = min{d(t), d(4) + a(4, t)} = min{∞, 6 + 2} = 8

Khoảng cách nhỏ nhất trong các nút chưa gán nhãn là d(2) = 7 nên gán nhãn cho nút 2 và cung (s, 2). Đường đi ngắn nhất hiện tại chứa cung (s, 3), (s, 1) (3, 4) và (s, 2). Đặt y = 2.

Bước 3: Đỉnh t chưa được gán nhãn nên quay lại bước 2.

Bước 2: Tính d(t) = min{d(t), d(2) + a(2, t)} = min{8, 7 + 2} = 8

Khi đó, nút t được gán nhãn cuối cùng và cung (4, t) cũng được gán nhãn là d(t). Do vậy, đường đi ngắn nhất cuối cùng chứa các cung (s, 3), (s, 1), (3, 4), (s, 2) và (4, t).

Đường đi ngắn nhất từ s đến t chứa các cung (s, 3), (3, 4) và (4, t) với chiều dài là 3 + 3 + 2 = 8. Đường đi này không chỉ là đường đi ngắn nhất duy nhất từ s đến t vì đường đi (s, 1), (1, 4) và (4, t) cũng có chiều dài bằng 8.

e- Đon gi

void Dijkstra(int sr,int ds,int path[])

{

struct node {

int pre; /* Nút trước */

int length; /* Độ dài giữa các nút */

enum {perm,tent} label; /* Enumeration for permanent and tentative labels */

}state[MAX];

int i,k,min; struct node *p;

/*Khởi tạo các nút tại bước đầu tiên của thuật toán */

for(p=&state[0];p<&state[no_nodes];p++)

{

p->pre= -1;

p->length=INFTY; p->label=tent;

} state[ds].length=0; /* Độ dài đến đích = 0 */ state[ds].label=perm; k=ds; /* Kiểm tra đường đi tốt hơn từ nút k ? */ do { for(i=0;i<no_nodes;i++) {

if(dist[k][i]!=0 && state[i].label==tent) { if((state[k].length+dist[k][i])< state[i].length) { state[i].pre=k; state[i].length=state[k].length + dist[k][i]; } } } k=0; min=INFTY; /*Tìm nút có nhãn nhỏ nhất */ for(i=0;i<no_nodes;i++) {

if(state[i].label==tent && state[i].length<min) { min=state[i].length; k=i; } } state[k].label=perm; } while(k!=sr); i=0; k=sr; /* Lưu vết đường đi ra mảng */ do {path[i++]=k;k=state[k].pre;} while(k>=0); path[i]=k; } f- Chng minh tính đúng đắn

cho đường đi có độ dài ngắn nhất giữa các đỉnh s và t trong một đồ thị có hướng có trọng số không âm.

Thật vậy, tại bước k ta có giả thiết quy nạp sau:

(i) nhãn của đỉnh x trong S là độ dài của đường đi ngắn nhất từ đỉnh s

đến đỉnh này và

(ii) nhãn của đỉnh x không thuộc S là độ dài của đường đi ngắn nhất từ đỉnh s tới đỉnh này và đường đi này chỉ chứa các đỉnh (ngoài chính

đỉnh này) thuộc S.

Khi k = 0, tức là khi chưa có bước lặp nào được thực hiện, S = {s}, vì thế độ dài của đường đi ngắn nhất từ s đến các đỉnh khác s là ∞ và độ dài của đường

đi ngắn nhất từ s đến chính nó bằng 0 (ởđây cho phép đường đi không có cung). Do đó, bước cơ sở là đúng.

Giả sử giả thiết quy nạp là đúng với bước k.

Gọi v là đỉnh không thuộc S ở cuối bước k có nhãn nhỏ nhất (nếu có nhiều đỉnh có nhãn nhỏ nhất thì có thể chọn một đỉnh nào đó làm v).

Từ giả thiết quy nạp ta thấy rằng trước khi vào vòng lặp thứ (k + 1), các

đỉnh thuộc S đã được gán nhãn bằng độ dài của đường đi ngắn nhất từ s. Đỉnh v cũng vậy, phải được gán nhãn bằng độ dài của đường đi ngắn nhất từ s. Nếu

điều này không xảy ra thì ở cuối bước lặp thứ k sẽ có đường đi với độ dài nhỏ

hơn dk(v) chứa cả đỉnh không thuộc S (vì dk(v) là độ dài của đường đi ngắn nhất từ s đến v chứa chỉ các đỉnh thuộc S sau bước lặp thứ k). Gọi u là đỉnh đầu tiên của đường đi này không thuộc S. Đó là đường đi với độ dài nhỏ hơn dk(v) từ s

đến u chứa chỉ các đỉnh của S. Điều này trái với cách chọn v. Vì thế, (i) vẫn còn

đúng ở cuối bước lặp (k+1).

Gọi u là đỉnh không thuộc S sau bước (k+1). Đường đi ngắn nhất từ s đến u chứa chỉ các đỉnh thuộc S sẽ hoặc là chứa v hoặc là không. Nếu nó không chứa v thì theo giả thiết quy nạp, độ dài của nó là dk(v). Nếu nó chứa v thì nó sẽ tạo thành đường đi từ s đến v với độ dài có thể ngắn nhất và chứa chỉ các đỉnh của S khác v, kết thúc bằng cạnh từ v đến u. Khi đó, độ dài của nó sẽ là dk(v) + a(v, u).

Điều này chứng tỏ (ii) là đúng vì dk+1(u) = min{dk(u), dk(v) + a(v, u)}.

g- Đánh giá thut toán

Tất cả thuật toán tìm đường đi ngắn nhất bao gồm hai phép toán số học cần thiết, phép cộng và phép chọn giá trị nhỏ nhất. Để phân tích độ phức tạp tính toán của một trong các thuật toán này, chúng ta cần một vài phép so sánh phép

cộng với phép chọn giá trị nhỏ nhất. Dĩ nhiên, phép so sánh này biến đổi giữa các máy tính (con người và máy móc) nhưng chúng ta giả sử rằng hai phép toán này cần số lượng thời gian tính toán tương đương.

Xét độ phức tạp tính toán của thuật toán Dijkstra. Trong lần lặp đầu tiên, có m – 1 đỉnh phải được kiểm tra. Từ phương trình (2.1) cần có m – 1 phép cộng, m – 1 phép chọn giá trị nhỏ nhất và chọn số nhỏ nhất trong m – 1 số, tức m -1 phép chọn giá trị nhỏ nhất khác. Vì vậy, cần 3(m – 1) phép toán cho bước lặp đầu tiên. Tương tự, 3(m – 2) phép toán cho lần lặp thứ hai, v.v... Tổng cộng, cần phép toán. Dĩ nhiên, tại mỗi lần lặp, phải xác định các đỉnh nào đã được gán nhãn và đỉnh nào chưa được gán nhãn. Điều này đòi hỏi thêm công việc, nhưng các kỹ thuật lập trình tốt có thể tránh được điều này [12][13]. Vì vậy, thuật toán đòi hỏi O(m2) thời gian thực hiện.

1 3( ) 3 ( 1) / 2 m i m i m m = − = − ∑

Một phần của tài liệu Một số vấn đề về mạng lưới trong cơ sở dữ liệu không gian (Trang 33)

Tải bản đầy đủ (PDF)

(91 trang)