Thứ Năm, 25 tháng 9, 2014

Thuật toán Floyd tìm đường đi ngắn nhất

Đường đi ngắn nhất giữa tất cả các cặp đỉnh


Rõ ràng ta có thể giải bài toán tìm đường đi ngắn nhất giữa tất cả các cặp đỉnh của đồ thị bằng cách sử dụng n lần thuật toán mô tả ở mục trước, trong đó ta sẽ chọn s lần lượt là các đỉnh của đồ thị. Rõ ràng, khi đó ta thu được thuật toán với độ phức tạp O(n4) (nếu sử dụng thuật toán Ford_Bellman) hoặc O(n3) đối với trường hợp trọng số không âm hoặc đồ thị không có chu trình. Trong trường hợp tổng quát, sử dụng thuật toán Ford_Bellman n lần không phải là cách làm tốt nhất. Ở đây ta sẽ mô tả một thuật toán giải bài toán trên với độ phức tạp tính toán O(n3): thuật toán Floyd. Thuật toán được mô tả trong thủ tục sau đây.
Procedure Floyd;
(* Tìm đường đi ngắn nhất giữa tất cả các cặp đỉnh
Đầu vào: Đồ thị cho bởi ma trận trọng số a[i,j], i, j =1, 2,. . ,n.
Đầu ra:
Ma trận đường đi ngắn nhất giữa các cặp đỉnh
d[i,j]=, i,j = 1, 2. . .,n,
trong đó d[i,j] cho độ dài đường đi ngắn nhất từ đỉnh i đến đỉnh j.
Ma trận ghi nhận đường đi
p[i,j], i, j = 1, 2.. . , n,
trong đó p[i,j] ghi nhận đỉnh đi trước đỉnh j trong đường đi ngắn nhất từ i đến j.
*)
begin
                  (* Khởi tạo *)
                  for i:=1 to n do
                       for j:=1 to n do
                       begin
                         d[i,j]:=a[i.j];
                          p[i.j]:=i;
                      end;
                 (* Bước lặp *)
                 for k:=1 to n do
                     for i:=1 to n do
                             for j:=1 to n do
                                if d[i,j]>d[i,k]+d[k,j] then
                               begin
                                      d[i,j]+d[i,k]+d[k,j];
                                      p[i,j]>p[k,j];
                               end;
end;
Rõ ràng độ phức tạp tính toán của thuật toán là O(n3).
Kết thúc chương này chúng ra trình bày một cách thể hiện thuật toán Dijkstra trên ngôn ngữ Pascal:

(* CHƯƠNG TRÌNH TÌM ĐƯỜNG ĐI NGẮN NHẤT TỪ ĐỈNH S ĐẾN ĐỈNH T THEO THUẬT TOÁN DIJKSTRA *)
uses crt;
        const max=50;
        var
          n, s, t:integer;
          chon:char;
          Truoc:array[1..max] of byte;
          d: array[1..max] of integer;
          a: array[1..max,1..max] of integer;
          final: array[1..max] of boolean;
procedure Nhapsolieu;
          var
          f:text;
          fname:string;
          i,j:integer;
          begin
                     write(‘Vao ten file du lieu can doc:’);
                     readln(fname);
                              assign(f,fname);
                     reset(f);
                     readln(f,n);
                              for i:=1 to n do
                                     for j:=1 to n do read(f, a[i,j];
                     close(f);
          end;
procedure Insolieu;
        var
         i,j:integer;
         begin
                      writeln(‘So dinh cua do thi:’,n);
                      writeln(‘Ma tran khoang cach:’);
                      for i:=1 to n do
                      begin
                              for j:=1 to n do write(a[i,j]:3,’ ‘);
                              writeln;
                     end;
         end;
Procedure Inketqua;
         Var
          i,j:integer;
          begin
                           writeln(‘Duong di ngan nhat tu ‘,s,’ den ‘,t);
                            write(t,’ ⇐ ’);
                            while i<> s do
                            begin
                                        i:=Truoc[i];
                                        write(i,’ ⇐ ’);
                            end;
            end;
Procedure Dijkstra;
           Var
           U,v,minp:integer;
           Begin
                                Write(‘Tim duon di tu s=’);Readln(s);Write(‘ den t=’);Readln(t);
                                For v:=1 to n do
                               Begin
                                       d[v]:=a[s,v];
                                       Truoc[v]:=s;
                                        Filal[v]:=false;
                                End;
                               Truoc[s]:=0;D[s]:=0;Final[s]:=true;
                               While not final[t] do (* Buoc lap *)
                                   Begin
                                                  Tim u la dinh co nhan tam thoi nho nhat
                                                   minp:=maxint;
                                                   for v:=1 to n do
                                                              if (not final[v]) ans minp>d[v]) then
                                                               begin
                                                                           u:=v;
                                                                           minp:=d[v];
                                                              end;
                                                  final[u]:=true;
                                                  if not final[t] then
                                                             for v:=1 to n do
                                                                   if (not final[v]) and (d[u]+a[u,v]<d[v]) then
                                                                   begin
                                                                               d[v]:=d[u]+a[u,v];
                                                                               Truoc[v]:=u;
                                                                   end;
                                   End;
                end;
Procedure Menu;
       Begin
                 Clrscr;
                 Writeln(‘1. Nhap du lieu tu file’);
                 Writeln(‘2. Giai bai toan’);
                 Writeln(‘3. Ket thuc’);
                 Writeln(‘---------------------‘);
                 Write(‘Hay chon chuc nang:’):
       End;
(* Chuong trinh chinhs *)
Begin
                     Repeat
                     Menu;
                     Chon:=readkey;
                                Writeln(chon);
                                Case chon of
                                               ‘1’:Nhapsolieu;
                                               ‘2’: begin
                                                               Insolieu;
                                                               Dijkstra;
                                                               Inketqua;
                                               ‘3’:exit;
                                                     end;
                      Until false;
End.

Source code: 
Liên hệ: vuthanhnam94@gmail.com

Thứ Tư, 24 tháng 9, 2014

Bài toán xếp hậu


Phân tích, cài đặt thuật toán quay lui để giải bài toán xếp hậu: xếp n con hậu vào bàn cờ n x n sao cho không con nào khống chế lẫn nhau.


Bài toán

Có một bàn cờ kích thước n x n (n ≥ 4). Yêu cầu: tìm tất cả các cách để đặt n con hậu trên bàn cờ sao cho không có con nào khống chế (*) lẫn nhau. Biết rằng, một con hậu có thể khống chế tất cả các quân cờ trên hàng ngay, cột dọc và hai đường chéo đi qua vị trí của nó.
01234567abcdefgh
08
17
26
35
44
53
62
71
Ký hiệu ô cờ để tiện lập trìnhKý hiệu ô cờ theo chuẩn quốc tế
Hình 1. Bàn cờ vua 8x8 (ký hiệu các ô cờ và ví dụ đặt 8 quân hậu)
(*) người ta thường sử dụng từ ăn, tuy nhiên, "hoàng hậu lại đi ăn các quân cờ" thì có vẻ không hợp lý nên T thay từ ăn bởi từ khống chế.

Phân tích

Biểu diễn bàn cờ

Do yêu cầu của bài toán là tìm các vị trí để đặt n quân hậu nên thay vì sử dụng mảng 2 chiều ta chỉ cần sử dụng mảng một chiều h[0..n-1] trong đó h[i] thuộc đoạn [0..n-1] là vị trí (cột) của quân hậu thứ i. Với ví dụ ở hình 1, mảng h sẽ là: [3 6 2 7 2 4 1 5] (quân hậu hàng 0 sẽ ở cột 3, quân hậu hàng 1 sẽ ở cột 6,...).

Đánh dấu các vị trí đã bị khống chế

Trong quá trình thử (đệ quy - quay lui), ta sẽ lần lượt đặt quân hậu tại một số vị trí trong bàn cờ. Mỗi lần thử đặt một quân hậu tại một ô nào đó thì phải đánh dấu các ô trong hàng, cột và hai đường chéo đi qua ô vừa đặt để các lần thử tiếp theo không đặt quân hậu nào khác vào các ô này (vì chũng đã bị khống chế bởi 1 quân hậu).
Để tiện trong việc trình bày ý tưởng cũng như lập trình, ta sẽ ký hiệu các đường chéo chính (theo hướng tây nam - đông bắc) và các đường chéo phụ (theo hướng tây bắc - đông nam) như hình 2. Ta thấy ô (ij) sẽ thuộc:
  • Đường chéo chính có số thứ tự là (i + j),
  • Đường chéo phụ có số thứ tự là (i - j + n).
Mỗi lần đặt một con hậu tại vị trí (ij) nào đó, ta phải đánh dấu các ô trên hàng i, cột j, đường chéo chính (i + j) và đường chéo phụ (i - j + n) để sau này không được đặt con hậu nào vào những vị trí này. Do đó, thay vì phải lưu phần đánh dấu cho tất cả các ô trên bàn cờ (nghĩa là cần một mảng n x n), ta chỉ cần đánh dấu cho các hàng, cột, đường chéo chính và đường chéo phụ tương ứng (nghĩa là cần hai mảng một chiều n phần tử để đánh dấu các hàng, cột và cần thêm hai mảng một chiều 2n+1 phần tử để đánh dấu các đường chéo).
0123456701234567
00123456707891011121314
1123456781678910111213
223456789256789101112
33456789 1034567891011
445678910114345678910
55678910111252456789
6678910111213612345678
77891011121314701234567
Số thứ tự các đường chéo chínhSố thứ tự các đường chéo phụ
Hình 2. Số thứ tự các đường chéo trên bàn cờ
(các số có màu đỏ thẩm)

Ý tưởng

Sử dụng thuật toán quay lui để thử đặt lần lượt n quân hậu trên bàn cờ:
  • Đầu tiên, thử đặt quân hậu 0 (ở hàng 0) vào các cột (có ncách đặt), Với mỗi vị trí của quân hậu này:
    • Thử đặt quân hậu 1 (ở hàng 1) vào các cột mà không bị quân hậu 0 khống chế, Với mỗi vị trí của quân hậu này:
      • Thử đặt quân hậu 2 (ở hàng 2) vào các cột mà không bị quân hậu 0 và quân hậu 1 khống chế, và cứ tiếp tục như vậy với các quân hậu tiếp theo.
Tìm được vị trí đặt cho quân hậu cuối cùng (mà không bị n-1 quân hậu trước đó khống chế) nghĩa là ta đã tìm được một phương án cho bài toán.
Mỗi lần thử đặt một con hậu tại một vị trí (i, j) nào đó thì phải đánh dấu khống chế cho cột j, đường chéo chính (i + j) và đường chéo phụ (i - j + n) sau đó mới thử đặt các con hậu tiếp theo (để khỏi đặt vào các hàng, cột, đường chéo đã bị các con hậu trước đó khống chế). Chú ý: do cách duyệt là từ hàng 0 đến hàng (n - 1) nên không cần phải đánh dấu khống chế cho hàng.
Khi quay lui để thử đặt vị trí khác thì phải hủy đánh dấu các hàng, cột và đường chéo tương ứng.

Bài đăng phổ biến