Bài toán Mã đi tuần và ứng dụng

6 2K 47
Bài toán Mã đi tuần và ứng dụng

Đang tải... (xem toàn văn)

Thông tin tài liệu

Bài toán Mã đi tuần và ứng dụng

Thuật toán hoàn chỉnh cho bài toán đi tuần một ứng dụng quan trọngCông HiệpTrong số báo tháng 6- 2003, có bài ″thuật toán hiệu quả giải bài toán đi tuần″ của tác giả Nguyễn Văn Trung, đây là một cách giải rất hay bởi bài toán này từ trước đến nay chỉ được xem xét dưới góc độ của một bài đệ quy quay lui bình thường với kích thước quá hạn chế. Hơn thế nữa thuật giải này còn cho thấy đệ quy quay lui có thể giải được những bài toán với kích thước tương đối nếu cài đặt tốt. Về mặt ý nghĩa hiệu quả là thế, tuy nhiên cài đặt của thuật toán này tôi thấy còn hơi thô sơ, chưa có sự chau chuốt về thuật toán kỹ thuật lập trình. Như tác giả bài viết đó đã nhận xét, với bài toán đó thì chỉ có thể giải được cho kích thước bàn cờ là không quá 30 (trong trường hợp có nghiệm), không những thế còn rất chậm. Sau khi nghiên cứu thuật giải này (thực chất là quay lui kết hợp với phương pháp duyệt ưu tiên Warnsdorff), tôi xin được trình bày những cải tiến có thể của thuật toán đã được trình bày một ứng dụng có thể nói khá hay vào việc giải một bài toán đã được nhiều người biết đến: ″bài toán tìm đường đi Hamilton″ của một đồ thị. Việc cải tiến dưới giúp chương trình chạy được với N = 100 ! I. Cải tiến chương trình Vẫn dựa trên tư tưởng của thuật toán đó là coi bàn cờ kích thước n x n như một đồ thị n * n đỉnh. Để hiểu một cách cặn kẽ bài viết này, các bạn nên tham khảo bài viết của tác giả đã được đăng trên số báo tháng 6. Cải tiến 1: Trước hết, xét một đỉnh u của đồ thị, chúng ta có thể dễ thấy là đỉnh này là biểu diễn của ô (x, y) trong đó: x := (u- 1) div n + 1; y := (u- 1) mod n + 1; với x là dòng y là cột hoàn toàn không phải xét các trường hợp như trước, các bạn nên kiểm tra tính đúng đắn của công thức này bằng việc vẽ hình bàn cờ để minh hoạ. Cải tiến 2: Để tránh cho chương trình của chúng ta chạy chậm (vì cài đặt của chúng ta là đệ quy quay lui), phải hết sức tránh việc gọi các chương trình con nhiều lần trong các thủ tục chính, điều này cài đặt của tác giả chưa đạt được. Do trong thủ tục Dat(u), vòng lặp ban đầu làm cho chúng ta phải xét tất cả N2 đỉnh của đồ thị tương đương với hàm kt(u, v) được gọi tới N2 lần, hơn nữa chương trình lại là đệ quy quay lui nên sẽ có rất nhiều lần gọi đến hàm này khiến chương trình tốn rất nhiều thời gian bộ nhớ. Đây là yếu tố chính khiến chương trình trên tuy có nhanh hơn chương trình duyệt bình thường nhưng chưa đạt được tốc độ đáng kể. Vậy nên chăng chúng ta hãy dùng một mảng để lưu xem từ đỉnh u có đến được đỉnh v không? Vấn đề đặt ra là trong trường hợp bàn cờ có kích thước lớn (chẳng hạn 100 x 100) thì đồ thị sẽ có n x n đỉnh, dù có dùng mảng động cũng không thể biểu diễn nổi. Để ý thấy rằng với một ô bất kỳ chỉ có thể tới nhiều nhất là 8 ô khác, điều này gợi ý ta nên dùng mảng 2 chiều, một mảng là số đỉnh của đồ thị, mảng kia là mảng 8 phần tử lưu lại 8 ô có thể tới được từ một đỉnh bất kì. Khai báo như sau: Type Tarr = array [1 8] of integer; Var A : array [1 100 * 100] of ^Tarr; Như vậy trong thủ tục Dat(u), ta sẽ xét tất cả các đỉnh có thể đến từ đỉnh u (nhiều nhất là 8 đỉnh) các đỉnh này đã được lưu lại trong mảng A. Caỉ tiến 3: Với cài đặt trước đây, để kiểm tra những đỉnh nào có thể đến được từ đỉnh u, ta phải xét tất cả n x n đỉnh. Tuy nhiên chúng ta có thể hạn chế điều này dựa vào nhận xét sau: Giả sử hai đỉnh u, v của đồ thị ở thế giao chân, dễ thấy |u - v| ≤ 2 x n + 1(*), vậy ta chỉ xét những đỉnh thoả mãn (*). Cải tiến 4: Để ý kỹ hơn chút nữa, trong hàm Bacnhonhat(u), bậc của đỉnh v ở thế giao chân với đỉnh u sẽ phải tính hai lần do đệ quy không có tính lưu lại, vậy ta có thể dùng một biến trung gian để số lần gọi đệ quy hàm này giảm đi (về mặt thời gian, nếu bài toán ra với kích thước nhỏ của bàn cờ thì cải thiện này không đáng kể, tuy nhiên với kích thước của bàn cờ lớn thì lượng thời gian sử dụng được tiết kiệm quá nhiều). Cũng xin lưu ý các bạn khi lập trình nên tránh gọi ít lần đệ quy nếu trong quá trình xử lý có một đối tượng nào đó được gọi đệ quy nhiều lần thì nên dùng thêm biến trung để lưu lại. Có thể minh hoạ lại đoạn chương trình đó như sau: function Bacnhonhat(u : integer) : byte; var v : integer; tg, C : byte; begin tg := 100; for v := 1 to 8 do if (a[u]^[v] <> 0) and (b[a[u]^[v]] = 0) then begin C := Dembac(a[u]^[v]); if tg > C then tg := C; end; Bacnhonhat := tg; end ; (Xin các bạn xem kỹ cài đặt ở sau để hiểu rõ, ở đây chỉ minh họa đó là dùng biến C để lưu kết quả của hàm Dembac(u) tránh gọi lại nó hai lần). Cải tiến 5: ở cuối bài viết, tác giả có nêu ra là trong trường hợp không có đường đi thì chưa xử lý được thì trong bài này chúng ta sẽ sử dụng thêm một biến đó là biến thời gian, ta dùng cận này để đánh giá. Khi chương trình của chúng ta chạy quá cận thời gian cho phép thì ta dừng ngay chương trình thông báo là không có đường đi. Điều này cũng dể hiểu bởi chương trình cài đặt của chúng ta có thể chạy khá nhanh trong trường hợp có nghiệm kích thước bàn cờ lên tới 100 x 100. Tôi cũng xin lưu ý khi các bạn đặt cận thời gian nên để hợp lý (khoảng 6s, tuỳ theo cấu hình máy của bạn. Với cấu hình của máy tôi: PIV, 1.70Ghz thì chỉ cần đặt cận là 3.5s là đủ) để đảm bảo tính chính xác, tốt nhất là xét với trường hợp kích thước bàn cờ là 100 x 100, ô xuất phát là ô (1, 1) lấy đó là cận. Dưới đây là toàn bộ phần cài đặt chương trình: {$S-} {$M 65520,0,655360} program Bai_toan_ma_di_tuan; const fi = ′ma.inp ′; fo = ′ma.out ′; max = 100; type TArr = array [1 8] of integer; var b : array [1 max * max] of integer; a : array [1 max * max] of ^TArr; n, x, y, count : integer; L : longint; function kt(u, v : integer) : boolean; var x1, y1, x2, y2 : integer; begin x1 := (u - 1) div n + 1; y1 := (u - 1) mod n + 1; x2 := (v - 1) div n + 1; y2 := (v - 1) mod n + 1; kt := (abs(x1 - x2) * abs(y1 - y2) = 2); end; procedure init; var u, i, m1, m2 : integer; j : byte; begin for i := 1 to n * n do begin new(a[i]); fillchar(a[i]^, sizeof(a[i]^), 0); end; for u := 1 to n * n do begin if u - 2 * n - 1 < 0 then m1 := 0 else m1 := u - 2 * n - 1; if u + 2 * n + 1 > n * n then m2 := n * n else m2 := u + n * 2 + 1; j := 0; for i := m1 to m2 do if kt(u, i) then begin inc(j); a[u]^[j] := i; end; end; end; function Dembac(u : integer) : byte; var v : integer; dem : byte; begin dem := 0; for v := 1 to 8 do if (a[u]^[v] <> 0) and (b[a[u]^[v]] = 0) then inc(dem); Dembac := dem; end; function Bacnhonhat(u : integer) : byte; var v : integer; tg, C : byte; begin tg := 100; for v := 1 to 8 do if (a[u]^[v] <> 0) and (b[a[u]^[v]] = 0) then begin C := Dembac(a[u]^[v]); if tg > C then tg := C; end; Bacnhonhat := tg; end; {thu tuc nay dung chuong trinh khi thoi gian cho phep chay chuong trinh de tim ra nghiem bi vuot qua} procedure Error; begin writeln('No solution'); halt; end; procedure PrintResult; var i, j : integer; begin for i := 1 to n * n do if i mod n = 0 then writeln(b[i] : 5) else write(b[i] : 5); writeln('Time : ', (MemL[0 : $46C] - L)/18.21 : 3 : 1); close(output); halt; end; procedure dat(u : integer); var v : byte; begin if (MemL[0 : $46C] - L)/18.21 > 3 then Error; for v := 1 to 8 do {xet tat ca cac dinh den duoc tu dinh u} if (a[u]^[v] <> 0) and (b[a[u]^[v]] = 0) and (Dembac(a[u]^[v]) = Bacnhonhat(u)) then begin inc(count); b[a[u]^[v]] := count; if count = n * n then PrintResult else dat(a[u]^[v]); dec(count); b[a[u]^[v]] := 0; end; end; begin assign(input, fi); reset(input); assign(output, fo); rewrite(output); readln(n, x, y); L := MemL[0 : $46C]; Init; x := (x - 1) * n + y; b[x] := 1; count := 1; dat(x); close(input); close(output); end . II. ứng dụng của thuật giải này vào bài toán tìm đường đi Hamilton Bài toán phát biểu một cách đơn giản như sau: Cho đồ thị G với n đỉnh có một số cạnh nối hai đỉnh. Xuất phát từ 1 đỉnh, hãy tìm một đường đi đi qua tất cả các đỉnh của đồ thị. Đường đi tìm được chính là đường đi Hamilton. Chúng ta có thể liên hệ bài toán này với thuật toán đã sử dụng để giải bài toán đi tuần để giải bài này. Tuy nhiên bài này có phần đơn giản hơn đó là giải với đồ thị, khác với bài trên đó là bài toán đi tuần phải biểu diễn bàn cờ bằng đồ thị. Bài toán tìm đường đi Hamilton vẫn thường được giải bằng đệ quy quay lui, tất nhiên với cách bình thường thì yêu cầu khi số đỉnh của đồ thị lớn là không thể. Vậy tôi xin trình bày với các bạn lại thuật toán cơ sở để giải bài đi tuần đã nói bài đường đi Hamilton như sau: Với một đỉnh u bất kì, xét tất cả các đỉnh kề với nó chọn ra đỉnh có bậc nhỏ nhất để xét tiếp, cứ như vậy cho đến khi tìm ra đường đi hoặc thông báo là không có. Cài đặt của bài này có phần đơn giản hơn bài trên, tôi xin phép không trình bày. Bạn nào cần thiết chương trình xin liên hệ với tác giả qua email. Mong nhận được ý kiến của các bạn đọc. . Thuật toán hoàn chỉnh cho bài toán mã đi tuần và một ứng dụng quan trọngCông HiệpTrong số báo tháng 6- 2003, có bài ″thuật toán hiệu quả giải bài toán mã đi. để giải bài toán mã đi tuần để giải bài này. Tuy nhiên bài này có phần đơn giản hơn đó là giải với đồ thị, khác với bài trên đó là bài toán mã đi tuần phải

Ngày đăng: 07/09/2012, 10:30

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan