Tải bản đầy đủ (.doc) (7 trang)

Bài toán đô thị thông minh

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (76.4 KB, 7 trang )

Thuật toán A* tìm đường đi ngắn nhất trên đồ thị có thông tin định hướng

Nguyễn Văn Sơn
Để giải bài toán tìm đường đi trên đồ thị chúng ta có rất nhiều thuật toán như Dijkstra,
thuật toán Floy-Bellman. Trong bài viết này, chúng tôi giới thiệu với bạn đọc một thuật
toán nổi tiếng tìm đường đi ngắn nhất trên đồ thị có thông tin định hướng với tên gọi A*
(đọc là ″a sao″).
Thuật toán này dựa trên tư tưởng tìm kiếm heuristic được áp dụng vào những bài toán
trong thực tế với với các thông tin đặc biệt được bổ sung ví dụ như: có thể tồn tại một
cung với trọng số lớn hơn tổng trọng số của các cung còn trên đồ thị, hoặc một đồ thị
không có cung nào. Thuật toán A* đặc biệt có hiệu quả khi áp dụng vào những bài toán
có kích thước lớn như bài toán tìm đường trong một mạng lưới giao thông có nhiều nút,
bài toán tìm đường đi trong các trò chơi chiến thuật với nhiều vật cản....Điểm mấu chốt
trong quá trình cài đặt thuật toán là việc xác định chính xác thông tin ước lượng nhằm
định hướng cho việc tìm kiếm. Chẳng hạn, trong bài toán tìm đường đi trong thành phố
thì thông tin ước lượng cho phép tìm ra đường tắt và tránh những vị trí hay bị tắc nghẽn;
còn trong các chương trình trò chơi, nó có thể định hướng đi tới đích nhanh nhất, nếu gặp
vật cản thì đi men theo vật cản.
Nội dung thuật toán được trình bày như dưới đây:
Một số ký hiệu:
- s: đỉnh xuất phát
- t: đỉnh kết thúc (đích)
- close: tập các đỉnh đã được tính chính xác đường đi ngắn nhất
- open: tập các đỉnh còn lại
- l[i,j]: trọng số của cung (i,j)
- d[i]: khoảng cách ngắn nhất từ i đến s.
- v[i]: khoảng cách ngắn nhất ước lượng từ i đến s.
Yêu cầu của bài toán là cần tìm giá trị của d[t].
Thuật toán A*
d[i] = +∞ i với mọi i thuộc [1..n]
close= [s]


open= [1..n] − [s]
k=s
repeat
{sửa đổi ước lượng min}
với mọi i thuộc open
d[i] = min {d[i], d[k] +l[k,i]}
{mở rộng tập close}
chọn k thuộc open để
(d[k] + v[k] ≤ d[i] + v[i]) với với mọi i thuộc open
open = open − [k]
close = close + [k]
until t thuộc close;


Thuật toán Dijkstra d[i] = +∞với mọi i thuộc [1..n]
close= [s]
open= [1..n] − [s]
k=s
repeat
{sửa đổi ước lượng min}
với mọi i thuộcopen
d[i] = min {d[i], d[k] +l[k,i]}
{mở rộng tập close}
chọn k thuộc open để
(d[k] ≤ d[i] ) vớivới mọi i thuộcopen
open = open − [k]
close = close + [k]
until t thuộc close;
Sự khác biệt duy nhất của hai thuật toán ở điểm chọn đỉnh k để mở rộng tập close. Việc
lựa chọn đỉnh nào để mở rộng cho quá trình tìm kiếm đóng vai trò rất quan trọng trong

chiến lược tìm kiếm. Nếu ta lựa chọn được đỉnh để phát triển tiếp với các ước lượng tốt
có thể rút ngắn thời gian và chi phí tính toán. Cả hai thuật toán ở trên đều có độ phức tạp
là O(n2). Người ta đã chứng minh được rằng nếu các trọng số của đồ thị đều là dương
(l[i,j]>0 với mọi i,j) và v[i] là ước lượng dương bé hơn so với đường đi ngắn nhất từ i tới
u (0< v[i] thời gian và cho ra kết quả chính xác. Dễ có nhận xét rằng nếu v[i] = 0 thì thuật toán n A*
trở thành thuật toán Dijkstra.
Vấn đề mấu chốt nhất khi xây dựng thuật toán A* cho các ứng dụng thực tế là việc tìm ra
hàm ước lượng v[i]. Sau đây chúng ta xem xét vài ví dụ đơn giản. Hãy xây dựng các ước
lượng cho thuật toán A* với bài toán chỉ ra đường đi ngắn nhất của một quân cờ di
chuyển trên một bàn cờ có kích thước vô tận và có một số ô được đánh dấu không được
đi vào. Chúng ta cần xây dựng v(x1,y1,x2,y2) là ước lượng của đường đi ngắn nhất từ ô
(x1,y1) tới ô (x2,y2). Việc xây dựng hàm này phụ thuộc vào quân cờ mà chúng ta xét tới
là loại gì. Dưới đây là minh hoạ về hàm lượng giá cho một số loại quân:
Với loại quân cờ chỉ có thể dịch chuyển một bước sang một trong bốn ô chung cạnh với ô
mà nó đang đứng, thì v(x1,y1,x2,y2)= | x1− x2 | +| y1 − y2 |
Với loại quân cờ chỉ có thể dịch chuyển một bước sang một trong tám ô chung cạnh với ô
mà nó đang đứng (ví dụ quân Vua trong cờ vua), thì v(x1,y1,x2,y2)= Max (| x1− x2 |,| y1
− y2 |)
Với loại quân cờ chỉ có thể dịch chuyển một bước sang một trong tám ô chéo 1/2 với ô
mà nó đang đứng (ví dụ quân Mã trong cờ vua), thì v(x1,y1,x2,y2)= (| x1− x2 | +| y1 − y2
|)/4
Dễ thấy, những ước lượng này đều thoả mãn tiêu chuẩn để thuật toán A*cho nghiệm
đúng, tức là chúng đều là các đánh giá thấp của đường đi ngắn nhất. Trong các trò chơi
thực tế, thông thường người ta sử dụng các biến thể của thuật toán A* và khi đó hàm ước
lượng thường rất phức tạp.
Sau đây chúng ta cùng xem xét một trò chơi phổ biến và khá đơn giản sau (trò chơi 8 −
puzzle):
Có 8 số mang các giá trị từ 1 tới 8 được sắp xếp vào một bảng các ô vuông kích thước
3x3. Mỗi số đó được xếp vào một ô, có một ô của bảng bỏ trống. Cho trước hai bảng các



con số (thể hiện cho trạng thái nguồn và trạng thái đích của bài toán), hãy chỉ ra một dãy
các phép chuyển các con số (nếu có) để thu được một một bảng (trạng thái đích) từ bảng
còn lại (trạng thái nguồn). Bạn chỉ được phép chuyển các con số sang một tron bốn ô
chung cạnh còn trống.

Trong bài toán này, ta sử dụng hàm đánh v với ý nghĩa: v(u) cho biết số các chữ số trong
trạng thái u không trùng với vị trí của nó trong trạng thái đích.
---------------- Chương trình nguồn minh hoạ cho thuật toán------------------{$B-}{$M 65000,0,655360}
Uses Crt;
Const Max=6;
fi='SO.INP';
fo='SO.OUT';
Type Vec=Array[1..Max,1..Max] of byte;
Item=record Ma:vec;Father,child:longint; End;
Pointer =^node; Node=record Infor:item;
Next:pointer; End;
Node1=array[1..4] of vec;Tp=0..10;
Var
U :item; Id :longint;
Front,rear,top:pointer; P :node1;
Dau,dich :vec; Found :boolean;
N :2..10; So_pts :tp;
I,j :tp;
ff:text;
PROCEDURE Vi_tri(a:vec;var k,t:TP);
Var i,j:1..max;
Begin For i:=1 to n do For J:=1 to n do
If a[i,j]=0 then

Begin K:=i; T:=j; Exit; End;
End;
{minh hoa cho cac phep di chuyen so}
PROCEDURE Left(a:vec;var lef:vec;i,j:tp);
Begin A[i,j]:=A[i,j+1];A[i,j+1]:=0; Lef:=A;
End;
PROCEDURE Right(a:vec;var righ:vec;i,j:tp);
Begin A[i,j]:=A[i,j-1]; A[i,j-1]:=0; Righ:=A;
End;
PROCEDURE Up(a:vec;var u:vec;i,j:tp);
Begin A[i,j]:=A[i+1,j]; A[i+1,j]:=0; U:=A;
End;


PROCEDURE Down(a:vec;var dow:vec;i,j:tp);
Begin A[i,j]:=A[i-1,j];A[i-1,j]:=0;Dow:=A
End;
PROCEDURE Pop(var Top:pointer;var U:Item);
Var Q:Pointer;
Begin Q:=Top;Top:=Top^.next;U:=Q^.infor; Dispose(q)
End;
PROCEDURE Ađ(u:Item;var rear,front:pointer);
Var Q:pointer;
Begin New(q); Q^.infor:=u;
Q^.next:=nil;
If front =nil then front:=q
Else rear^.next:=q;Rear:=q
End;
FUNCTION Count(a:vec):byte;
Var i,j :0..10; Dem :byte;

Begin Dem:=0; For i:=1 to n do For j:=1 to n do
If a[i,j]<>Dich[i,j] then dem:= dem+1;Count:=Dem
End;
FUNCTION Dung(a,b:vec):boolean;
Var i,j:tp;
Begin Dung:=false;
For i:=1 to n do For J:=1 to n do
If a[i,j]<>b[i,j] then exit;Dung:=true;
End;
FUNCTION Ktr(Front ,top:pointer;a:vec):boolean;
Var q :Pointer; Ok :boolean;
Begin Ok:=true; Q:=top;
While (q<>nil) and ok do
if Dung(Q^.infor.ma,A) then ok:=False
Else Q:=q^.next; Q:=Front;
While (q<>nil) and ok do
if Dung(Q^.infor.ma,a) then ok:=False
Else Q:=q^.next; Ktr:=ok
End;
PROCEDURE Nhap;
Var i,j:1..max;
Begin assign(ff,fi);
reset(ff);
readln(ff,n);
For i:=1 to n do
For j:=1 to n do
Read(ff,dau[i,j]);
For i:=1 to n do
For j:=1 to n do
Read(ff,dich[i,j]);



close(ff)
End;
PROCEDURE In_vec(a:vec);
Var i,j:byte;
Begin For i:= 1 to n do
Begin For j:=1 to n do
If a[i,j]<>0 then write(ff,a[i,j]:3) else write(ff,' ');
writeln(ff);
End;
End;
FUNCTION Nhap_cx(a:vec):Boolean;
Var i,j :byte; S1,s2 :Set of byte;
Begin S1:=[];s2:=[];
For I:=0 to N*N-1 do s1:=s1+[i];
Nhap_cx:=False; For i:=1 to n do
For J:=1 to n do
If not(a[i,j] in s1) or (a[i,j] in s2)then exit
Else S2:=s2+[a[i,j]];
Nhap_cx:=true;
End;
PROCEDURE Init;
Var U:pointer;
Begin Clrscr;
Nhap;
If not Nhap_cx(dau)or not Nhap_cx(dich) then
begin Writeln('LOI KHI DOC DU LIEU');
readln;
halt;

end;
Found:=false;New(U);
U^.infor.ma:=Dau;U^.infor.father:=0;
U^.infor.child:=1;U^.next:=nil;
Top:=U;front:=nil;Rear:=nil;
End;
PROCEDURE Ptu_ke(var p:node1;U:vec;i,j:tp;var so_pts:tp);
Var v :vec;
Begin If I>1 then Begin Down(u,v,i,j);
If ktr(front,top,v) then
Begin So_pts:=so_pts+1; P[so_pts]:=v;End
End;
If j>1 then
Begin Right(u,v,i,j);
If ktr(front,top,v) then
Begin So_pts:=so_pts+1;P[so_pts]:=v End
End;
If j


If ktr(front,top,v) then
Begin So_pts:=so_pts+1; P[so_pts]:=v; End
End;
If I
If ktr(front,top,v) then
Begin So_pts:=so_pts+1;P[so_pts]:=v;End
End;
End;
PROCEDURE Sxep(Var P:node1;so_pts:tp);
Var Tg :vec; T,i,j,min :tp; B :array[1..4] of byte;

Begin For i:=1 to so_pts do B[i]:=count(p[i]);
For i:=1 to so_pts-1 do
Begin Min:=i;
For j:=i+1 to so_pts do
If b[j] If min>i then
Begin T:=b[i]; B[i]:=b[min];B[min]:=t;
Tg:=p[i];P[i]:=p[min];P[min]:=tg; End
End
End;
PROCEDURE Push(var top:pointer;p:node1;child:longint;so_pts:tp);
Var i:tp;v:pointer;
Begin For i:=so_pts downto 1 do Begin
New(v); V^.infor.ma:=p[i];
V^.infor.father:=child; Id:=id+1;
V^.infor.child:=id;
V^.next:=top; Top:=v;End
End;
PROCEDURE In_Kq;
Var p :pointer;i,buoc :longint;
Begin Buoc:=0;
P:=front;I:=1;
assign(ff,fo);
rewrite(ff);
if not found then
begin
write(ff,0);
close(ff);
exit;
end;
P:=p^.next;

While P<>nil do
Begin If p^.infor.father=i then
Begin
I:=p^.infor.child; Buoc:=buoc+1;
Writeln(ff,buoc);In_vec(P^.infor.ma);
End; P:=p^.next


End;
close(ff);
End;
Begin
Clrscr;Init;
While (top<>nil) and not(Found) do
Begin Pop(Top,U); Ađ(u,Rear,Front);
If dung(u.ma,dich) then found:=true
Else Begin Vi_tri(U.ma,i,j); So_pts:=0;
Ptu_ke(p,u.ma,i,j,so_pts);
sxep(p,so_pts);Push(top,p,u.child,so_pts) End
End;
In_kq;
End.
Bài tập: Cho một ma trận nhị phân (các số là 0 hay 1) kích thước 500 x 500. Hai ô được
gọi là kề nhau nếu chúng cùng chứa số 0 và chung cạnh. Hai ô gọi là đi được đến nhau
nếu chúng có thể đi được đến nhau qua một số bước dịch chuyển qua các ô kề nhau, số
bước dịch chuyển là độ dài của đường đi. hãy tìm đường đi ngắn nhất từ ô (x1,y1) tới ô
(x2,y2).




Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×