Bài toán tìm đường đi ngắn nhất

Một phần của tài liệu [Giáo trình] Phân tích thiết kế thuật toán và đánh giá độ phức tạp của giải thuật - ĐH Sư phạm Hà Nội (Trang 87 - 90)

5.1. Một số khái niệm cơ bản

Cho đồ thị có trọng số G = (V, E, w), trong đó G = (V, E) là một đồ thị, w là một hàm trọng số từ E đến R, gán mỗi cung e ∈ E với một giá nào đó w(e) ∈ R.

Định lí 5.3. Cho đồ thị có trọng số G = (V, E, w). Nếu P = (v1, v2, …, vk) là một đường đi ngắn nhất trên từ đỉnh v1 đến đỉnh vk thì mọi dãy con từ đỉnh vi đến đỉnh vj (1 ≤ i ≤ j ≤ k) trên đường đi đó đều là đường đi ngắn nhất từ đỉnh vi đến đỉnh vj.

Hầu hết các thuật toán tìm đường đi ngắn nhất đều dùng qui hoạch động (ví dụ các thuật toán Floyd và Dijkstra dùng kết quả của định lí trên) hoặc phương pháp tham lam đúng (ví dụ thuật toán Kruskal).

Bài toán đo khong cách

Phát biểu bài toán: Giả sử đồ thị không có chu trình âm và có ma trận trọng số là c[1..n, 1..n]. Hãy tìm đường đi ngắn nhất từ đỉnh xuất phát s đến đỉnh kết thúc t.

Để giải bài toán, ta sử dụng kí hiệu δ(u, v) để chỉ độ dài đường đi từ đỉnh u đến đỉnh v trong đồ thị.

Ý tưởng của thuật toán: Trước tiên tìm đỉnh v1≠ t mà δ(s, t) = δ(s, v1) + c[v1, t]. Dễ thấy luôn tồn tại đỉnh v1 đó và v1 là đỉnh đứng ngay trước đỉnh t trên đường đi ngắn nhất từ s đến t. Nếu v1 = s thì đường đi ngắn nhất cần tìm chính là cạnh (s, t). Nếu không thì vấn đề trở thành tìm đường đi ngắn nhất từ s đến v1 và ta lại tìm đỉnh v2≠ {t, v1} mà δ(s, v1) = δ(s, v2) + c[v1, v2], … Cứ tiếp tục như vậy, sau một số hữu hạn bước, ta có dãy t = v0 , v1, v2, …, vk = s không chứa các đỉnh lặp lại. Lật ngược thứ tự dãy như vậy ta có đường đi ngắn nhất từ s đến t.

Nhãn khong cách và phép co

Các bài toán tìm đường đi ngắn nhất xuất phát từ một đỉnh s xác định đều sử dụng kĩ thuật gán

nhãn khoảng cách. Hiểu "nhãn khoảng cách" theo nghĩa như sau: Với mỗi đỉnh v ∈ V, nhãn khoảng cách d[v] là độ dài của một đường đi nào đó từ đỉnh xuất phát s đến đỉnh v.

Ban đầu ta chưa xác định được bất kỳ đường đi nào từ s đến các đỉnh khác nên nhãn d[v] được khởi tạo như sau: Với ∀ v ∈ V thì

⎩ ⎨ ⎧ ≠ ∞ + = = , 0 ] [ s v if s v if v d

Do định nghĩa của nhãn khoảng cách nên ta có d[v] ≥ δ(s, v) với ∀v ∈ V. Các thuật toán tìm đường đi ngắn nhất từ s đến v sẽ tìm cách cực tiểu hóa dần các nhãn khoảng cách d[v] cho đến khi d[v] = δ(s, v) với ∀ v ∈ V bằng cách sử dụng các phép co.

Giả sử đã xác định được d[u]. Xét đường đi từ s đến u. Ta nối thêm cạnh (u,v) (nếu có cạnh này) để được đường đi từ s đến v, và có độ dài là d[u] + c[u,v]. Nếu xảy ra bất đẳng thức:

d[u] + c[u, v] < d[v]

thì ta cần thay đường đi từ s đến v bằng đường đi từ s đến v mà ngay trước đỉnh v là đỉnh u cần đi qua, tức là:

d[v] = d[u] + c[u, v]

Khi đó ta nói rằng d[v] được giảm bởi phép co (u, v).

5.2. Thuật toán Dijkstra

Chú ý rằng thuật toán Dijkstra chỉ áp dụng đối với đồ thị G có trọng số không âm.

Định nghĩa: Một nhãn khoảng cách d[v] là cố định nếu ta đã biết d[v] = δ(s, v) và không thể giảm d[v] được nữa thông quan một phép co, ngược lại ta nói nhãn d[v] là tự do.

Thut toán Bước 1: Khi to

- Khởi tạo nhãn khoảng cách d[s] := 0;

- d[v] := a[s, v], ∀ v ≠ s (a là ma trận kề của đồ thị).

- Khởi tạo mọi trạng thái nhãn là tự do: avail[v] := true, ∀ v ≠ s.

Bước 2: Lp hai thao tác sau

- Cốđịnh nhãn: Chọn trong số các định có nhãn tự do, lấy ra đỉnh u có d[u] nhỏ nhất, ghi nhận nó trở thành đỉnh cố định, tức avail[u] := false;

- Sửa nhãn: Dùng đỉnh u, xét tất cả các đỉnh v kề đỉnh u và thực hiện phép co theo cạnh (u, v) để cực tiểu hóa d[v]. Tại bước này, nếu dùng phép co (u, v), ta nên lưu vết bằng mảng pred bởi lệnh pred[v] := u. Việc lưu vết này giúp lần lại đường đi ngắn nhất dựa vào mảng pred sau khi thuật toán Dijkstra hoàn tất.

Bước 3: Truy vết

Sử dụng mảng lưu vết pred để tìm lại đường đi ngắn nhất từ s đến t với độ dài đường đi ngắn nhất là d[t]. (adsbygoogle = window.adsbygoogle || []).push({});

Mô phng thut toán Dijkstra trên Pascal

procedure Dijkstra; s u v d[v] d[u] c[u,v]

begin (*Khởi tạo*) for v ∈ V do begin d[v] := a[s, v]; pred[v] := s; avail[v] := true; end; d[s] := 0; avail[s]:= false; stop := false; (*Bước lặp*) while not stop do begin

(*Cốđịnh nhãn*)

d[u] := min {d[v] : ∀ v ∈ V và avail[v] = true}; avail[u] := false ;

(*Sửa nhãn*) for v ∈ V do if avail[v] then

if d[v] > d[u] +a[u, v] then begin d[v] := d[u] +a[u, v]; pred[v] := u; end; end; (* kiếm tra điều kiện kết thúc lặp*) stop := true; for v ∈ V do

if avail[v] then stop := false; end;

Một phần của tài liệu [Giáo trình] Phân tích thiết kế thuật toán và đánh giá độ phức tạp của giải thuật - ĐH Sư phạm Hà Nội (Trang 87 - 90)