Ứng dụng Dijkstra Fibonacci heap

Một phần của tài liệu (LUẬN văn THẠC sĩ) thuật toán dijkstra fibonacci heap, thuật toán ACO tìm đường đi tối ưu và ứng dụng (Trang 55)

13.1.1. Phát biểu bài toán 1

Cho một mạng giao thông gồm N nút, các nút được đánh số từ 1 đến N. Từ nút i đến nút j có không quá một đường đi một chiều với độ dài là c(i, j). Cho trước hai nút giao thông s và t, hãy tìm đường đi ngắn nhất từ s đến t.

3.1.2. Mô hình hoá bài toán

Ta có thể coi mạng giao thông trong bài toán 1 như là một mô hình đồ thị có hướng, có trọng số như sau: Mỗi nút giao thông là một đỉnh của đồ thị, mỗi đoạn đường một chiều nối trực tiếp hai nút giao thông là một cung của đồ thị. Việc tìm đường đi ngắn nhất từ nút giao thông s đến t trở thành việ tìm

đường đi ngắn nhất từ đỉnh s đến t trên trên đồ thị có hướng G = (V, E), với V là tập các đỉnh ứng với các nút giao thông, E là tập các cung ứng với các đoạn đường một chiều nối trực tiếp hai nút như đã nói ở trên.

3.1.3. Mô tả input, output

Đồ thị được cho ở dạng danh sách cung để có thể thử nghiệm với các đồ thị có số đỉnh lớn. Mặt khác để thuận lợi khi nhập dữ liệu chúng ta sẽ cho input trong các file text.

Cụ thể:

Input: Được cho trong file text có tên là DONG.INP có dạng như sau:

Dòng đầu là 4 số nguyên dương N,M, s, t, trong đó N là số đỉnh, M là số cung của đồ thị, s là đỉnh xuất phát, t là đỉnh đích cần đến. M dòng tiếp theo, mỗi

dòng ghi 3 số nguyên dương u, v, l với ý nghĩa có cung từ u đến v có độ dài

(trọng số) là l.

Output: Kết quả ghi ra file text có tên DONG.OUT có dạng như sau:

Dòng đầu là một số nguyên T là độ dài đường đi ngắn nhất tìm được.

Các dòng sau ghi số hiệu các đỉnh của đường đi ngắn nhất từ s đến t.

3.1.4. Một số kiểu dữ liệu và các biến trong chương trình

Chương trình sử dụng một kiểu con trỏ được khai báo dạng sau:

type tro = ^node; node = record

sohieu, degree: longint; key: int64;

parent, child, left, right: tro; mark : boolean;

end;

Trường sohieu của một node là tên của đỉnh mà node đó đại diện, các trường khác trong node hiểu như đã trình bày ở chương 1.

Chương trình sử dụng biến mảng d dạng d: array[1..nmax] of tro; Mảng này thực chất là chứa n biến trỏ, mỗi biến trỏ trỏ vào một node của Fibonacci heap, d[i] trỏ vào node đại diện cho đỉnh i của đồ thị, trường

key của node này là nhãn khoảng cách từ đỉnh s đến đỉnh i. Việc dùng mảng d này cho phép chương trình có thể truy nhập vào các nút trong Fibonacci heap

khi biết sohieu của node này.

Các biến mảng adj[i], head[j] dùng để lưu đồ thị ở dạng danh sách kề

kiểu móc nối.

3.1.5. Một số hàm và thủ tục trong chương trình

Thủ tục Enter: Thủ tục này có nhiệm vụ đọc dữ liệu từ tệp input, sau khi thực hiện thủ tục này số đỉnh của đồ thị được lưu trong biến n, số cung

lưu trong biến m. Đồ thị được lưu trữ dưới dạng danh sách kề như sau:

Với mỗi đỉnh i thì các đỉnh kề với i được xác định thông qua head[i],

nếu h[i] = k  0 thì adj[k]. v là đỉnh kề thứ nhất của i. Địa chỉ của đỉnh kề tiếp

theo của i là adj[k].link: Nếu adj[k].link = k1 0 thì đỉnh kề thứ 2 của i là

adj[k1].v. Quá trình tìm đỉnh kề với i kết thúc khi địa chỉ tìm được bằng 0.

Như vậy nếu ngay từ đầu head[i] = 0 thì danh sách kề của i bằng rỗng, nghĩa là từ i không có cung nối tới bất kỳ đỉnh nào cả.

Thủ tục INSERT(var x, minH:tro): Thủ tục này chèn một nút mới được trỏ bởi con trỏ x vào Fibonacci heap được trỏ bởi minH. Trong thủ tục này x

minH đều là các tham số hình thức (khi gọi thủ tục thì tham số hình thức

phải thay bằng các biến tương ứng).

Thủ tục FIB_HEAP_LINK(var y, x:tro): Biến gốc y (được trỏ bởi con trỏ y) làm con của gốc x (y và x đều là các tham số hình thức).

Thủ tục CUT(var minH, x, y : tro): Đưa nút x ra khỏi danh sách con

của nút y, biến x thành một gốc. MinH, x, y đều là các tham số hình thức. Thủ tục CASCADING_CUT(var minH, y : tro): Thủ tục cắt liên hoàn như đã trình bày ở chương 1.

Thủ tục FIB_HEAP_DECREASE_KEY(var minH, x: tro; k: longint): Thủ tục này sẽ thay x^.key bằng một giá trị mới nhỏ hơn là k, nếu k nhỏ hơn khóa của nút y là cha của x thì sẽ cắt nhánh cây gốc x bằng thủ tục CUT(x, y).

Thủ tục Consolidate(var minH : tro): Thủ tục này sẽ thống nhất danh sách gốc, nghĩa là sẽ ghép hai gốc có cùng số con thành một gốc, việc này sẽ được lặp cho đến khi không tồn tại hai gốc có số nút con bằng nhau.

Thủ tục FIB_HEAP_EXTRACT_MIN(var z : tro): Thủ tục này thực hiện xóa nút cực tiểu ra khỏi H, đồng thời thực hiện việc thống nhất các gốc đã bị trì hoãn ở các phép toán khác. Đoạn mã nguồn sau mô tả thủ tục FIB_HEAP_EXTRACT_MIN

procedure FIB_HEAP_EXTRACT_MIN(var z : tro); // input: Fibonacci heap H.

//output: Nút có khóa cực tiểu của H. var x, tam, tt, tam2 : tro;

i: longint; begin z := minH; if z <> NIL then begin nH := nH - 1; x:= z^.child;

if x <> nil then // biến các con của z thành gốc begin

tam := x;tam^.parent := nil; while tam^.right <> x do begin tam := tam^.right; tam^.parent := nil; end; x^.left^.right := minH^.right;

minH^.right^.left := x^.left; minH^.right := x;

x^.left := minH; end;

z^.right^.left := z^.left; // gỡ z khỏi danh sách gốc z^.left^.right := Z^.right;

if z = z^.right then // Fibonacci heap chỉ có duy nhất mình nút z minH := NIL

else minH := z^.right;

if (minH <> nil) and (minH <> minH^.right) then Consolidate(minH);

end

else writeln('dong rong'); end;

Hàm Relax(u, v: longint; w: longint): Hàm này trả lại giá trị true nếu nhãn khoảng cách từ s đến v lớn hơn khoảng cách từ s đến u cộng với khoảng cách trực tiếp từ u đến v. Ngoài ra khi hàm có giá trị là true thì đỉnh u được ghi nhận là đỉnh đứng ngay trước v trên đường đi ngắn nhất từ s đến t.

Thủ tục Dijkstra Fibonacci heap có thể viết lại như sau:

Procedure Dijkstra; // input: Đồ thị có hướng G, s, t. // output: Khoảng cách từ s đến t. var j, i: longint; tt, tam, ta : tro; begin repeat u:= nil; FIB_HEAP_EXTRACT_MIN(u);

if (u = nil) or (u^.sohieu = t) then Break;

i := head[u^.sohieu]; while i <> 0 do begin

if Relax(u^.sohieu, adj[i].v, adj[i].w) then

FIB_HEAP_DECREASE_KEY(minH,d[adj[i].v], d[u^.sohieu]^.key +adj[i].w); i := adj[i].link;

end;

until (u = nil) or (nh = 0); end;

Thủ tục Init: Thủ tục này tạo ra một Fibonacci heap gồm N nút, được quản lý bởi biến toàn cục minH.

Thủ tục PrintResult: Đưa ra output.

3.1.6.Sơ đồ thuật toán

Sơ đồ thuật toán giải bài toán tìm đường đi ngắn nhất có dạng sau:

// input: Đồ thị có hướng G, s, t.

// output: Khoảng cách và đường đi từ s đến t. BEGIN Enter; Init; dijkstra; PrintResult; END.

3.1.7. Các kết quả thực nghiệm giải bài toán 1

Thực nghiệm kiểm tra chương trình trên 11 bộ test có tên lần lượt là Test00, Test01,.., Test09, Test10. Các file input, output được lưu trong các thư mục có tên ở cột thứ nhất. Hình thức một file output đã mô tả ở mục 3.1.3, dưới đây là nội dung file output của bộ test thứ nhất - Test00:

341

1 25097 23364 18174 7928 10086 38541 4461 11683 34627 30000 Kết quả (thời gian chạy chương trình) thực nghiệm khi chạy chương trình giải bài toán 1 được thống kê trong bảng 3.1 dưới đây (đơn vị thời gian tính bằng giây, N là số đỉnh, M là số cạnh):

Bảng 3.1. Thống kê kết quả thực nghiệm chương trình giải bài toán 1

Tên test

N M

Thời gian chạy chương trình dùng Dijkstra heap

Thời gian chạy chương trình dùng Dijkstra thường Test00 40000 1600000 1.900 11.807 Test01 100 1000 0.056 0.040 Test02 1000 20000 0.067 0.110 Test03 10000 60000 0.094 0.174 Test04 20000 200000 0.412 3.994 Test05 40000 600000 0.980 9.758 Test06 80000 1000000 1.398 6.196 Test07 100000 2000000 2.587 19.570 Test08 300000 3000000 2.923 70.682 Test09 1000000 5000000 7.573 > 3000 Test10 20000 400000 0.269 2.148

Tên các test ở cột 1 của bảng trên thực chất là tên thư mục chứa test đó, điều cần quan tâm nhất ở đây là thời gian chạy của hai chương trình ở cột 4 và cột 5. Số liệu ở bảng trên đã chứng minh tính ưu việt của thuật toán Dijkstra Fibonacci heap so với thuật toán Dijkstra nguyên thủy.

Nhìn chung khi đồ thị thưa và số đỉnh càng lớn thì thuật toán Dijkstra Fibonacci heap càng tỏ ra ưu việt hơn so với thuật toán Dijkstra nguyên thủy. Tuy nhiên khi số đỉnh ít thì có khi thuật toán Dijkstra nguyên thủy chạy lại nhanh hơn (test01). Sở dĩ có hiện tượng này là vì: Độ phức tạp của thuật toán Dijkstra nguyên thủy là O(N2) suy ra số phép tính toán của thuật toán này cỡ C1N2, độ phức tạp của thuật toán Dijkstra Fibonacci heap là O(Nlog N + M)

suy ra số phép tính toán thực tế của thuật toán là cỡ C2(Nlog N + M) với C1,

C2 là các hằng số. Do Dijkstra Fibonacci heap phải thao tác với Fibonacci heap tương đối phức tạp nên thực tế C2 > C1 vì thế khi N nhỏ có thể xảy ra

trường hợp C1N2 < C2(Nlog N + M), nếu điều này xảy ra thì thuật toán

Dijkstra nguyên thủy chạy nhanh hơn.

Các tệp input trong các bộ test được tạo ra hoàn toàn ngẫu nhiên, thuật toán tạo các file input như sau:

- N, M, s, t nhập từ bàn phím.

- Dùng một mảng c lưu bán bậc ra của các đỉnh, ban đầu c[i]:= 0

i=1..N. Thực hiện M lần sinh số ngẫu nhiên nguyên dương thuộc [1, N] bằng

hàm random, khi sinh được số i thì c[i] được tăng 1 đơn vị. - Tạo các cung xuất phát từ các đỉnh i như sau:

Với mỗi đỉnh i: Dùng một mảng daxet để quản lý các đỉnh đã có cung

nối từ đỉnh i tới, ban đầu daxet[j]:=false  j<>i, daxet[i] := true. Thực hiện c[i] lần sinh số nguyên ngẫu nhiên u thuộc [1, N] sao cho daxet[u] = false.

Với mỗi u sinh được ta có một cung (i,u), trọng số của cung này cũng được

sinh ngẫu nhiên. Sau khi ghi cung (i,u) này ra tệp input ta sẽ gán cho daxet[u] bằng true để không lặp lại các cung.

Thuật toán như đã mô tả ở trên cho phép ta tạo ra các file input lưu các đồ thị ở dạng danh sách cung với số đỉnh lớn tới hàng triệu và không có cung nào bị lặp.

3.2. Ứng dụng Dijkstra Fibonacci heap, ACO giải bài toán TSP mở rộng mở rộng

3.2.1. Phát biểu bài toán 2

Cho một mạng giao thông gồm N nút, các nút được đánh số từ 1 đến

N. Từ nút i đến nút j có không quá một đường đi một chiều với độ dài là c(i,j). Một người bán hàng, xuất phát từ nút giao thông s, phải đưa hàng đến k nút giao thông phân biệt cho trước rồi quay trở về s.

Hãy tìm một hành trình cho người bán hàng sao cho độ dài của hành

trình này là ngắn nhất có thể được.

3.2.2. Mô hình hoá bài toán

Ta có thể coi mạng giao thông trong bài toán 2 như là một mô hình đồ thị có hướng, có trọng số như sau: Mỗi nút giao thông là một đỉnh của đồ thị, mỗi đoạn đường một chiều nối trực tiếp hai nút giao thông là một cung của đồ thị. Việc tìm đường đi ngắn nhất từ nút giao thông s, qua k nút giao thông cho trước rồi trở về s được quy về bài toán tìm đường đi ngắn nhất từ đỉnh s, qua k đỉnh cho trước rồi quay về đỉnh s trên trên đồ thị có hướng G = (V ,E), với V là tập các đỉnh ứng với các nút giao thông, E là tập các cung ứng với các đoạn đường một chiều nối trực tiếp hai nút như đã nói ở trên.

3.2.3. Mô tả input, output

Đồ thị được cho ở dạng danh sách cung mặt khác để thuận lợi khi nhập dữ liệu chúng ta sẽ cho input trong các file text. Cụ thể:

Input: Được cho trong file text có tên là TSP.INP có dạng như sau: Dòng đầu là 4 số nguyên dương N, M, s, k, trong đó N là số đỉnh, M là số

cung của đồ thị, s là đỉnh xuất phát còn K là số đỉnh cho trước phải đi qua. M dòng tiếp theo, mỗi dòng ghi 3 số nguyên dương u, v, l với ý nghĩa có cung từ

u đến v có độ dài (trọng số) là l. Dòng cuối cùng ghi k số nguyên dương phân

biệt là tên các đỉnh cần phải đi qua.

Output: Kết quả ghi ra file text có tên TSP.OUT có dạng như sau:

Dòng đầu là một số nguyên T là độ dài đường đi ngắn nhất tìm được. Các dòng sau ghi số hiệu các đỉnh của đường đi ngắn nhất xuất phát từ s thỏa mãn yêu cầu của bài toán.

3.2.4. Thuật toán tổng quát giải bài toán 2

Thuật toán tổng quát giải bài toán 2 gồm hai bước cơ bản sau:

Bước 1: Xây dựng đồ thị G1 = (V1, E1) như sau:

V1 gồm k +1 đỉnh của đồ thị đã cho là s và k đỉnh cố định phải đi qua

theo yêu cầu của bài toán.

E1 được xây dựng như sau: Hai đỉnh i, j thuộc E1 thì trọng số của cung (i, j) là C(i,j) = D(i,j), trong đó D(i,j) là khoảng cách từ i đến j trên đồ thị G đã cho.

Để cha ̣y được với N, M lớn ta sẽ dùng Dijkstra Fibonacci heap để tính D(i, j).

Bước 2: Dùng thuật toán ACO (MMAS) tìm chu trình xuất phát từ s đi

qua mỗi đỉnh đúng một lần trên đồ thị G1 (việc làm này chính là giải bài toán người chào hàng trên đồ thị G1).

3.2.5. Một số hàm và thủ tục trong chương trình

Chương trình sử dụng thuật toán Dijkstra Fibonacci heap nên các thủ tục thực hiện các thao tác đối với Fibonacci heap như đã trình bày trong mục

3.1.5 cũng được sử dụng trong chương trình giải bài toán 2. Ngoài ra chương trình còn sử dụng thêm một số thủ tục sau:

Thủ tục Make_graph: Xây dựng đồ thị G1. Thủ tục này hoạt động

như sau: Từ mỗi đỉnh u trên đồ thị G1 ta dùng thủ tục Dijkstra Fibonacci heap tìm khoảng cách từ đỉnh đó đến tất cả các đỉnh còn lại trên đồ thị G, từ đó ta cũng tính được khoảng cách từ u đến các đỉnh trên G1.

Đoạn mã chương trình dưới đây mô tả thủ tục Make_graph:

procedure make_graph;

// input: Đồ thị G. Output: đồ thị G1. var i:integer;

begin

for i:= 1 to k1 + 1 do

for j := 1 to k1 + 1 do c[i,j] := vocung; for i := 1 to k1+1 do begin init(tendinh[i]); dijkstra(tendinh[i]); for j:= 1 to k1 + 1 do if i = j then c[i,j] := vocung

else c[i,j] := d[tendinh[j]]^.key; end;

end;

Thủ tục tkcb: Thủ tục này dùng để tìm một chu trình trong G1 bằng

Thủ tục lotteryweel(k : longint): Thủ tục này chính là Thủ tục chọn

ngẫu nhiên đã trình bày ở chương 2, thủ tục này cho phép xác định đỉnh kế

tiếp sẽ đi khi một con kiến đang ở đỉnh k.

Thủ tục pheromoneupdat: Cập nhật lại vết mùi như đã trình bày ở chương 2.

Thủ tục init1: Khởi tạo vết mùi và giá trị ban đầu cho một số đại lượng.

Thủ tục MMAS_cycle: Thủ tục này dùng để tìm chu trình ngắn nhất trong G1, xuất phát từ s thỏa mãn yêu cầu của bài toán. Đoạn mã nguồn dưới đây mô tả thủ tục MMAS_cycle:

procedure MMAS_cycle; var l: int64;d : longint; s:real; Begin init1; repeat inc(buoclap); for i:= 1 to m do begin libest := vocung; w[1] :=1; fillchar(daxet,sizeof(daxet),false); fillchar(p,sizeof(p),0); daxet[1] := true; l:= 0; for j:= 2 to n do begin lotteryWheel(j);

l := l + c[w[j-1],w[j]]; end; l := l + c[w[n],1]; if l< libest then begin libest := l; ibest := w; end; end;

if libest < lgbest then begin lgbest := libest; gbest := ibest; tmax:= m/lgbest; tmin:= tmax/10; end; pheromoneupdat; until (buoclap >= N_C); End;

3.2.6. Sơ đồ tổng quát của thuật toán giải bài toán 2.

Begin Enter; Make_graph; MMAS_cycle; PrintResult; End.

3.2.7. Các kết quả thực nghiệm giải bài toán 2

Thực nghiệm tiến hành trên 3 bộ test, mỗi bộ test được chương trình thực hiện 06 lần. Mỗi lần thực hiện, chương trình sẽ tự động khởi tạo lại cường độ vết mùi nếu qua 10000 vòng lặp mà độ dài G-best không thay đổi. Kết quả được

Một phần của tài liệu (LUẬN văn THẠC sĩ) thuật toán dijkstra fibonacci heap, thuật toán ACO tìm đường đi tối ưu và ứng dụng (Trang 55)

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

(74 trang)