CHƯƠNG 5

ĐƯỜNG ĐI NGẮN NHẤT  TRÊN ĐỒ THỊ

Tôn Quang Toại Khoa CNTT, Đại học Ngoại ngữ ‐ Tin học TP.HCM

Nội dung

Các khái niệm mở đầu Phát biểu bài toán Thuật toán Dijkstra Thuật toán Ford – Bellman  Thuật toán Floyd

Các khái niệm mở đầu

Đồ thị có trọng số: Đồ thị G=(V, E) có trọng số khi mỗi cạnh e=(u,v) của đồ thị được gán với một con số w(u,v) và được gọi là trọng số của cạnh e.

(cid:2870)

(cid:2869)

Đường đi: Đường đi giữa 2 đỉnh s và t được cho bởi là dãy đỉnh (cid:3040) trong đó:

(cid:2869)

(cid:3040)

(cid:3036)

(cid:3036)(cid:2878)(cid:2869)

Các khái niệm mở đầu

Độ dài đường đi: Độ dài đường đi là tổng trọng số của tất cả các cạnh trên đường đi đó

(cid:3040)(cid:2879)(cid:2869)

(cid:3036)(cid:2880)(cid:2869)

𝑤 𝑝 (cid:3404) (cid:3533) 𝑤(cid:4666)𝑥(cid:3036), 𝑥(cid:3036)(cid:2878)(cid:2869)(cid:4667)

Phát biểu bài toán

Ví dụ:

18

b

c

12

p = (a, d, f, g) w(p) = 6 + 8 + 2 = 16

4

3

2

7

g

6

a

d

8

2

3

9

4

f

e

14

Các khái niệm mở đầu

Biểu diễn đồ thị trọng số Cách 1: Ma trận trọng số

𝑣(cid:3036)(cid:3037) (cid:3404) (cid:4682) 0/∞ 𝑛ế𝑢 (cid:4666)𝑖, 𝑗(cid:4667) ∉ 𝐸 𝑤(cid:4666)𝑖, 𝑗(cid:4667) 𝑛ế𝑢(cid:4666)𝑖, 𝑗(cid:4667) ∈ 𝐸

int[,] v; int n;

Các khái niệm mở đầu

Cách 2: Danh sách cạnh LinkedList> e;

Cách 3: Danh sách kề LinkedList>[] v;

Phát biểu bài toán

Phát biểu bài toán:

Cho đồ thị G=(V, E) có trọng số và hai đỉnh s và t  cho trước. Hãy tìm 1 đường đi từ s đến t sao cho độ dài  đường đi là ngắn nhất

Phát biểu bài toán

18

c

b

12

Ví dụ:

4

3

2

7

p = (a, d, f, g) w(p) = 6 + 8 + 2 = 16

g = t

6

s = a

d

8

2

3

9

4

f

e

pshortest = (s,                   ,t) w(pshortest) =

14

Thuật toán Dijkstra

Giả định của thuật toán Dijkstra Không có cạnh âm trong đồ thị s và t liên thông với nhau

Ý tưởng thuật toán Dijkstra:

Mỗi đỉnh   của đồ thị, ta gán một giá trị dist[i]: là  độ dài đường đi ngắn nhất từ s đến i. Nhiệm vụ: Giảm giá trị mảng dist[ ] từng bước.

Thuật toán Dijkstra

Thuật toán Dijkstra:

Input: G=(V, E) và 2 đỉnh s, t Output: 𝑑𝑖𝑠𝑡(cid:4670)(cid:4671)

Bước 1 (Khởi tạo)

• 𝑑𝑖𝑠𝑡 𝑖 (cid:3404) (cid:3397)∞, ∀𝑖 ∈ 𝑉 • 𝑑𝑖𝑠𝑡 𝑠 (cid:3404) 0 • Tập S=V (S là tập đỉnh chưa xét)

Bước 2 (Lặp)

• Tìm min:  Chọn đỉnh 𝑎 ∈ 𝑆 sao cho 𝑑𝑖𝑠𝑡 𝑎  có giá trị nhỏ nhất

• Cập Nhật: Với mọi đỉnh b kề đỉnh a

– Nếu dist[b]>dist[a] + w(a,b) thì dist[b]=dist[a] + w(a,b)  và pre[b]=a

• S = S ‐ {a} (đỉnh a đã xét xong, bỏ khỏi tập S)

Lặp cho đến khi t  S (đỉnh t được xét)

Thuật toán Dijkstra

Giải thích bước cập nhật:

Khi gán 𝑑𝑖𝑠𝑡 𝑏 (cid:3404) 𝑑𝑖𝑠𝑡 𝑎 (cid:3397) 𝑤 𝑎, 𝑏 tức là ta đã phát hiện ra 1 đường đi mới đến b có độ dài ngắn hơn

𝑑𝑖𝑠𝑡(cid:4666)𝑏(cid:4667) 𝑏

𝑑𝑖𝑠𝑡 𝑏 (cid:3408) 𝑑𝑖𝑠𝑡 𝑎 (cid:3397) 𝑤(cid:4666)𝑎, 𝑏(cid:4667)

Đường đi cũ

Đường đi mới

𝑤(cid:4666)𝑎, 𝑏(cid:4667)

𝑠

𝒂 𝑑𝑖𝑠𝑡(cid:4666)𝑎(cid:4667)

Và đường đi đó trước khi tới b phải qua a vì vậy để tìm liệt kê các đỉnh đường đi chúng ta dùng 1 mảng pre[]. Trong đó pre[b]=a cho biết đỉnh ngay trước đỉnh b là đỉnh a.

Ví dụ

Ví dụ 1: Dùng thuật toán Dijkstra Tìm đường đi  ngắn nhất từ a đến g

18

c

b

12

4

3

2

7

g=t

6

d

s=a

8

2

3

9

4

f

e

14

Ví dụ

Lưu ý:

Thuật toán Dijkstra có thể áp dùng tìm đường đi  ngắn nhất từ một đỉnh đến tất các các đỉnh còn lại  (chúng ta chỉ cần sửa lại điều kiện kết thúc vòng lặp  là S=Ø)

Ví dụ

Ví dụ 2: Tìm đường đi ngắn nhất từ đỉnh c đến các đỉnh còn lại theo thuật toán Dijkstra

b d f 5 5

4 3

3 2 2 h a 1 1

3 1

6 c 3 g e

Cài đặt

Cấu trúc dữ liệu:

dist[i]: Lưu độ dài đường đi ngắn nhất từ s đến i

pre[i]: Lưu đỉnh trước của đỉnh i

label[i]:

true: 𝒊 ∉ 𝑺 false: 𝒊 ∈ 𝑺

path[]: lưu các đỉnh của đường đi

Cài đặt

Input

LinkedList>[] v; int s;

Output

int[] dist; bool[] label; int[] pre; LinkedList path;

Cài đặt

void Dijkstra(int s) { // B1: Khởi tạo for (int i=0; i

for (int i=0; i

// B2: Lặp: Tìm min + cập nhật for (int k=0; k

Cài đặt

void TruyVet(int s, int t) { path = new LinkedList();

for (i=t; i!=-1; i=pre[i]) path.AddFirst(i); }

Thuật toán Bellman – Ford

Thuật toán Dijkstra không thể thực hiện khi đồ  thị có cạnh âm (đồ thị vô hướng) hay đồ thị có  chu trình âm có thể đến được (đồ thị có  hướng)

Thuật toán Bellman – Ford xác định các chu  trình âm hay trả về cây đường đi ngắn nhất

Thuật toán Bellman – Ford

Thuật toán Bellman – Ford

• 𝑑(cid:2868) 𝑖 (cid:3404) (cid:3397)∞, ∀𝑖 ∈ 𝑉 • 𝑑(cid:2868) 𝑠 (cid:3404) 0 • k=0

Bước 1 (Khởi tạo)

– 𝑑(cid:3038) 𝑠 (cid:3404) 0 –  𝑑(cid:3038) 𝑏 (cid:3404) min 𝑑(cid:3038)(cid:2879)(cid:2869) 𝑎 (cid:3397) 𝑤 𝑎, 𝑏

• Kiểm tra:

– Nếu  𝑑(cid:3038) 𝑖 (cid:3404) 𝑑(cid:3038)(cid:2879)(cid:2869) 𝑖 , ∀𝑖 ∈ 𝑉 (tức là 𝑑(cid:3038) 𝑖  đã ổn định) thì dừng thuật toán

Bước 2 (Lặp) • k = k+1 • Cập Nhật: Với mọi đỉnh a kề̀ b

Lặp cho đến khi k=n

Bước 3: Nếu k=n thì đồ thị có chu trình âm

Ví dụ

Ví dụ 1: Dùng thuật toán Bellman – Ford Tìm  đường đi ngắn nhất từ a đến các đỉnh còn lại

18

b

c

12

4

3

2

7

g

6

s=a

d

8

2

3

9

4

f

e

14

Ví dụ

Ví dụ 2: Tìm đường đi ngắn nhất từ đỉnh a đến  các đỉnh còn lại bằng thuật toán Bellman – Ford

2 4 6 5 5

4 3

3 2 2 8 1 1 1

3 1

6 3 3 7 5

Ví dụ

Ví dụ 3: Tìm đường đi ngắn nhất từ đỉnh a đến  các đỉnh còn lại bằng thuật toán Bellman – Ford

b e 5

2 4 7

a 3 6 f

-6

3

6

6 c d

Thuật toán Floyd – Warshall

Thuật toán Floyd – Warshall: Thuật toán Floyd  – Warshall cho chúng ta cách tìm đường đi ngắn nhất giữa tất cả các cặp đỉnh hoặc chỉ ra đồ thị có chu trình âm

Thuật toán Floyd – Warshall

Thuật toán Floyd – Warshall:

Input: G=(V, E) Output:  • Ma trận đường đi ngắn nhất giữa các cặp đỉnh  – d[i, j]: độ dài đường đi ngắn nhất từ đỉnh i đến đỉnh j

– pre[i, j]: ghi nhận đỉnh đi trước đỉnh j trong đường đi ngắn nhất

từ đỉnh i đến đỉnh j

• Ma trận ghi nhận đường đi

Thuật toán Floyd – Warshall

Thuật toán Floyd – Warshall:

Bước 1 (Khởi tạo)

• 𝑑 𝑖, 𝑗 (cid:3404) (cid:4682) 𝑎(cid:4670)𝑖, 𝑗(cid:4671), (cid:4666)𝑖, 𝑗(cid:4667) ∈ 𝑉 (cid:3397)∞, (cid:4666)𝑖, 𝑗(cid:4667) ∉ 𝑉

for (int k=0; kd[i,k]+d[k,j]) { d[i,j] = d[i,k]+d[k,j]; pre[i,j] = pre[k,j]; }

• pre 𝑖, 𝑗 (cid:3404) 𝑖 Bước 2 (Lặp)

Bài tập

Bài tập 1: Tìm đường đi ngắn nhất từ u đến các  đỉnh còn lại bằng thuật toán Dijkstra

s 7 r

1 3 4 3 x

u 1 2 3 t

1 4

w z y 3 5

Bài tập

Bài tập 2: Cho đồ thị có trọng số G = (V, E), V = {  v1, v2, v3, v4, v5, v6 , v7} xác định bởi ma trận  trọng số D. Dùng thuật toán Dijkstra tìm đường  đi ngắn nhất từ v1 đến các đỉnh v2, v3, v4, v5,  v6, v7

Bài tập

0

5

31

40

27 0

0 26

 73 49

D

 8 0

 25 38 16

   70

 0

 0 23 10

 9 12 0

   

          

          

Bài tập

Bài tập 3: Cho một ví dụ chứng tỏ rằng thuật toán Dijkstra để tìm đường đi ngắn nhất từ một đỉnh đến các đỉnh khác không áp dụng được cho đồ thị có trọng số nếu có cạnh có trọng số âm

Bài tập

Bài  tập  4:  Dùng  thuật  toán  Dijsktra  để  tìm  đường đi ngắn nhất từ đỉnh a đến các đỉnh còn  lại trong đồ thị vô hướng có trọng số sau:

b f d 5 5

7 4

3 2 z 2 1 a

4 3

g c e 6 5

Chọn lựa thuật toán

Thuật toán

Lý do chọn

Tùy ngữ cảnh của bài toán chúng ta phải chọn lựa thuật toán tìm đường đi ngắn nhất cho phù hợp STT 1

Dijkstra

• Tìm đường đi ngắn nhất từ một đỉnh đến các đỉnh còn lại • Đồ thị chỉ có cạnh trọng số dương • Số đỉnh lớn: 𝑛, 𝑚 (cid:3409) 10(cid:2874) • Độ phức tạp: 𝑂(cid:4666)𝑚. log (cid:4666)𝑛(cid:4667)(cid:4667)

2

Bellman – Ford

• Tìm đường đi ngắn nhất từ một đỉnh đến các đỉnh còn lại • Đồ thị có thể có chu trình âm • Độ phức tạp: 𝑂(cid:4666)𝑚. 𝑛(cid:4667)

3

Floyd – Warshall

• Tìm đường đi ngắn nhất giữa tất cả cập đỉnh • Số đỉnh nhỏ: 𝑛 (cid:3409) 500 • Độ phức tạp: 𝑂 𝑛(cid:2871)

Tóm tắt chương 5

Thuật toán Dijkstra Thuật toán Bellman – Ford  Thuật toán Floyd – Warshall