Thuật toán Loang theo lớp
Trang 1toán loang theo lớp
Nguyễn Tuấn Dũng
Tìm kiếm theo chiều rộng (Breadth First Search - BFS) - còn gọi là thuật toán loang và tìm
kiếm theo chiều sâu (Depth First Search - DFS) là 2 thuật toán cơ bản trong lý thuyết đồ thị Mặc dù thuật toán DFS với cấu trúc dữ liệu kiểu ngăn xếp (Stack) tuân theo quy luật LIFO - Last in, First out (vào sau, ra trước) có thể cài đặt dễ dàng bằng đệ quỵ Nhưng vẫn nảy sinh những bài toán mà để giải quyết nó thuận tiện hơn cần có một thuật toán tìm kiếm khác, đó là BFS Với cấu trúc dữ liệu kiểu hàng đợi (Queue), tuân theo quy luật FIFO - First in, First out (vào trước, ra trước) Cài đặt thuật toán loang không cần dùng đệ quy và cũng không đến nỗi phức tạp, đó là một ưu điểm Hơn nữa, thuật toán tìm kiếm theo chiều sâu 'đi sâú vào đồ thị, lưu các đỉnh trên đường đi vào ngăn xếp, còn thuật toán tìm kiếm theo chiều rộng 'quét ngang' đồ thị và lưu các đỉnh vào hàng đợị Như ta biết, có thể tồn tại nhiều đường đi từ một đỉnh A đến đỉnh B trên đồ thị Nếu bằng BFS tìm được một đường
đi đầu tiên từ đỉnh A đến đỉnh B thì đường đi đó sẽ là đường đi qua ít đỉnh nhất từ A để đến được B Nhưng nếu bằng DFS thì ngược lại, đường đi tìm được đầu tiên chưa chắc đã
là đường đi qua ít đỉnh nhất Mà trong một số bài toán, mặc dù vấn đề cần giải quyết có thể được biến đổi, làm cho phức tạp hơn bởi người ra đề nhưng chung qui vẫn có thể đưa về việc tìm đường đi sao cho phải qua ít đỉnh nhất Khi đó cài đặt thuật toán loang là giải pháp thích hợp Đối với BFS cơ bản thì các bạn đã từng gặp trên các số báo trước đây của ISM Tuy nhiên, trong các bài toán cụ thể, mặc dù vẫn với yêu cầu trên nhưng chúng ta lại phải
sử dụng thuật toán loang một cách linh hoạt và sáng tạo mới đạt được kết quả mong đợị Một ví dụ minh hoạ cho điều đó được đưa ra trong cách giải bài toán MEET - 'Gặp gỡ' (thi Quốc gia 1998 -1999) dưới đây:
Trên một lưới ô vuông M*N (M,N < 100), người ta đặt robot A ở góc trái trên, robot B ở góc phải dướị Mỗi ô của lưới có thể đặt một vật cản hoặc không (ô trái trên và phải dưới không có vật cản) Hai robot bắt đầu di chuyển đồng thời với tốc độ như nhau và không robot nào được dừng lại trong khi robot kia di chuyển (trừ khi nó không thể đi được nữa) Tại mỗi bước, robot chỉ có thể di chuyển theo 4 hướng - đi lên, đi xuống, sang trái, sang phải - vào các ô kề cạnh Hai robot sẽ gặp nhau nếu chúng đứng trong cùng một ô vuông Bài toán đặt ra là tìm cách di chuyển ít nhất mà 2 robot phải thực hiện để có thể gặp nhau
Dữ liệu vào trong file Meet.inp :
- dòng đầu ghi 2 số M,N
- M dòng tiếp theo, mỗi dòng ghi N số 0 hoặc 1 mô tả trạng thái của các ô vuông: 1-có vật cản, 0-không có vật cản
Các số trên cùng một dòng của file dữ liệu cách nhau ít nhất một dấu trắng
Kết quả ghi ra file Meet.out :
- nếu 2 robot không thể gặp nhau thì ghi ký tự #
- Ngược lại, ghi hai dòng, mỗi dòng là một dãy các lý tự viết liền nhau mô tả các bước đi của robot : U-đi lên, D-đi xuống, L- sang trái, R- sang phảị Dòng đầu là các bước đi của A, dòng sau là của B
Ví dụ:
Meet.inp
4 6
Trang 20 1 1 0 0 0
0 0 0 0 0 1
0 0 1 0 0 1
0 1 0 1 0 0
Meet.out
DRRR
LULU
Với dạng băi kiểu năy thì ai cũng nghĩ ngay đến thuật toân loang Nhưng loang như thế năỏ vì ta có hai robot cùng di chuyển Vă tất nhiín, băi toân năy trở nín hay hơn so với câc băi loang thông thường Chắc hẳn ai cũng biết một thí nghiệm phổ biến về sự giao thoa sóng: 'cho 2 chiếc kim (không câch xa nhau lắm) dao động cùng tần số trín mặt nước, khi
đó có thể quan sât thấy những vòng tròn đồng tđm trín mặt nước cứ loang rộng dần ra cho đến một lúc năo đó, 2 vòng tròn đầu tiín được tạo ra bởi sự dao động của 2 chiếc kim năy gặp nhau, giao thoa với nhaú
Từ thí nghiệm đó, chúng ta có thể đưa ra câch di chuyển cho 2 robot theo kiểu loang chiều rộng, nhưng chỉ loang từng lớp, từng lớp một đối với mỗi robot, giống như 2 vòng tròn loang rộng dần ra trín mặt nước
Về mặt ý tưởng thì sự loang của 2 robot lă song song, đồng thời với nhaụ Nhưng bắt tay văo lăm cụ thể thì không cho phĩp chúng ta căi đặt thuật toân theo kiểu song song ở đđỵ Vì thế câch giải quyết của tôi lă sử dụng một vòng lặp (Procedure Strati-BFS) thực hiện luđn phiín 2 thủ tục: cứ cho robot 1 loang ra một lớp mới xung quanh (Procedure BFSRobot1) thì dừng lại để cho robot 2 cũng loang ra một lớp xung quanh khâc (Procedure
BFSRobot2) Vòng lặp sẽ kết thúc nếu hai lớp vừa mới loang của 2 robot năy giao nhau (PathFound - 2 robot sẽ gặp được nhau) hoặc cả hai robot đều không thể di chuyển được nữa (Stoped - băi toân vô nghiệm) Tôi tạm gọi đđy lă thuật toân 'loang theo lớp' Bản chất năy thực ra đê tồn tại trong câch lặp của thuật toân loang cơ bản vă ở đđy, việc khai thâc được nó giúp chúng ta giải quyết tốt vấn đề đặt rạ Tuy nhiín, khâi niệm lớp ở đđy nín hiểu như thế năỏ Câc bạn đê biết trong câch lặp của BFS thì tại mỗi lần lặp, ta sẽ lấy ra (get out) câc đỉnh trong hăng đợi từ Queue[first] đến Queue[last] để đưa văo (put in ) hăng đợi câc đỉnh khâc kề với câc đỉnh đê lấy rạ Sau đó câc biến first, last sẽ được điều chỉnh để trở thănh vị trí đầu vă vị trí cuối của đoạn chứa câc đỉnh mới năy trong Queue (xem Procedure BFSRobot1; vă Procedure BFSRobot2;) Chúng ta gọi tập hợp câc đỉnh từ Queue[first] đến Queue[last] trong mỗi bước lặp của BFS lă một lớp
Dưới đđy lă chương trình giảị Trong trương trình có sử dụng một số thủ thuật cơ bản khâ quen thuộc trong băi toân tìm đường đi trín lưới ô vuông như kỹ thuật 'rẳ - viền xung quanh mảng A bằng câc số 1 để không phải kiểm tra điều kiện vượt khỏi giới hạn của lưới trong quâ trình di chuyển robot: A[0,j] = A[M+1,j] = A[i,0] = A[i,N+1] = 1 (i = 1, , M ; j
= 1, N) Vă 2 mảng hằng Hi, Hj giúp ta có thể dùng vòng for để thực hiện 4 hướng đi L,
U, R, D dễ dăng hơn trong khi loang, thuận tiện cho việc viết chương trình
Mảng New1 được dùng để đânh dấu câc ô vuông thuộc lớp mă robot 1 vừa loang tới, khi cho robot 2 loang nếu gặp phải một trong câc ô năy thì nghĩa lă tìm được nghiệm của băi toân Có thể tiết kiệm bộ nhớ nếu không dùng New1, mă đânh dấu ngay bằng mảng A với câch cộng thím 100 văo A[x,y] nếu ô (x,y) thuộc lớp mă robot 1 vừa loang tới (vì A[x,y] chỉ ghi toăn số 1 hoặc số 0)
Trang 3Hai mảng Q1, Q2 là hàng đợi cho việc loang robot 1 và robot 2 Nếu cần tiết kiệm bộ nhớ
có thể sử dụng chung một hàng đợi Q cho việc loang của 2 robot, robot 1 sử dụng phần đầu
từ Q[1] trở đi còn robot 2 sử dụng phần cuối từ Q[max*max] trở lại, vì tổng số ô vuông mà
cả 2 robot đi qua không vượt quá kích thước lưới M*N cho đến khi hai lớp loang của 2 robot giao nhau, mà vòng lặp lại được kết thúc ngay tại đây (tôi đã thử cài đặt kiểu này, chương trình vẫn chạy ra kết quả đúng)
Còn việc đánh dấu đường đi được thực hiện bởi cách cộng thêm k vào A[x,y] nếu ô(x,y) là
ô được đi đến từ ô trước đó theo hướng k (số tự nhiên k chạy từ 1 đến 4 lần lượt chỉ 4 hướng đi của robot: L, U, R, D) xm,ym là toạ độ ô vuông 2 robot gặp nhaụ Phải sử dụng thêm biến pred2 để ghi nhận hướng mà từ đó Robot 2 đi tới (xm,ym) vì A (xm,ym) đã dùng để ghi nhận hướng mà từ đó Robot 1 đi đến ô gặp nhau Việc tìm lại đường đi cho từng robot được thực hiện trong procedure FindS1S2
Trong khuôn khổ bài báo, không thể giải thích kỹ chương trình, vì vậy các bạn có thể nghiên cứu chi tiết thêm qua chương trình giải cụ thể dưới đây:
Uses Crt;
Const Inf = 'Meet.inp';
Outf = 'Meet.out';
Max = 100;
Hi : Array[1 4]of integer = (0,-1,0,1);
Hj : Array[1 4]of integer = (-1,0,1,0);
Type Square=record x,y:byte; end;
Var A : Array[0 Max+1,0 Max+1]of byte;
Q1,Q2 : Array[1 Max*Max]of square;
New1 : Array[0 Max+1,0 Max+1]of Boolean;
First1,Last1,First2,Last2,N,M,Pred2,Xm,Ym : Word;
PathFound,Stoped : boolean;
S1,S2 : string;
Procedure Readinp;
var f:text; i,j:word;
Begin
fillchar(A,sizeof(A),1); {rào xung quanh}
assign(f,inf); reset(f);
readln(f,M,N);
for i:=1 to M do
begin
for j:=1 to N do read(f,a[i,j]);
readln(f);
end;
close(f);
End;
Procedure Init;
Begin
first1:=1; last1:=1;
Q1[1].x:=1; Q1[1].y:=1;
first2:=1; last2:=1;
Q2[1].x:=M; Q2[1].y:=N;
Trang 4Procedure BFSRoBot1;
var x,y,i,j,k:word;
begin
j:=last1;
for i:=first1 to last1 do
for k:=1 to 4 do
begin
x:=Q1[i].x + Hi[k];
y:=Q1[i].y + Hj[k];
if A[x,y]=0 then
begin
inc(A[x,y],k);
inc(j);
Q1[j].x:=x; Q1[j].y:=y;
New1[x,y]:=true;
end;
end;
first1:=last1+1; last1:=j;
if first1>last1 then Stoped:=true; end;
Procedure BFSRoBot2;
var x,y,i,j,k:word;
begin
j:=last2;
for i:=first2 to last2 do
begin
for k:=1 to 4 do
begin
x:=Q2[i].x + Hi[k];
y:=Q2[i].y + Hj[k];
if A[x,y]=0 then
begin
inc(A[x,y],k);
inc(j);
Q2[j].x:=x; Q2[j].y:=y;
end;
if New1[x,y] then
begin
PathFound:=true;
Pred2:=k;
xm:=x; ym:=y;
Exit;
end;
end;
end;
Trang 5first2:=last2+1; last2:=j;
if first2>last2 then Stoped:=true; end;
Procedure StratiBFS;
Begin
PathFound:=False;
Stoped:=False;
Repeat
fillchar(New1,sizeof(New1),false); BFSRoBot1;
BFSRoBot2;
Until Pathfound or Stoped;
End;
Procedure FindS1S2;
var x,y,k:byte;
begin
s1:=''; s2:='';
x:=xm; y:=ym;
{RoBot1}
repeat
k:=A[x,y];
case k of
1 : s1:=s1+'L';
2 : s1:=s1+'U';
3 : s1:=s1+'R';
4 : s1:=s1+'D';
end;
x:=x-Hi[k];
y:=y-Hj[k];
until (x=1)and(y=1);
{RoBot2}
x:=xm; y:=ym; A[x,y]:=Pred2; repeat
k:=A[x,y];
case k of
1 : s2:=s2+'L';
2 : s2:=s2+'U';
3 : s2:=s2+'R';
4 : s2:=s2+'D';
end;
x:=x-Hi[k];
y:=y-Hj[k];
until (x=M)and(y=N);
end;
Procedure WriteOut;
var f:text; i,j:byte;
Trang 6assign(f,outf); rewrite(f);
If not(pathfound) then
begin
write(f,'#'); close(F); exit;
end;
FindS1S2;
for i:=length(s1) downto 1 do write(f,s1[i]);
writeln(f);
for i:=length(s2) downto 1 do write(f,s2[i]);
close(f);
end;
BEGIN
Readinp;
Init;
StratiBFS;
WriteOut;
END
Chúng ta cần chú ý rằng, bài toán có thể có nhiều nghiệm vì tồn tại nhiều cách đi từ góc trái trên đến góc phải dướị Hoặc, tuỳ thuộc vào trạng thái của lưới, hai robot sẽ có thể không bao giờ gặp nhaụ Trường hợp đó xảy ra khá hiển nhiên khi không tồn tại đường đi
từ ô(1,1) đến ô(M,N), hoặc tồn tại, nhưng đường đi đó có độ dài chẵn (ta tạm gọi độ dài đường đi là tổng số đỉnh trên đường đi đó) Vì hai robot chỉ có thể gặp nhau nếu như có một đường đi độ dài lẻ từ ô(1,1) đến ô(M,N) Có thể chứng minh điều này: giả sử 2 robot gặp nhau ở ô(xm,ym) thì đường đi từ ô(1,1) đến ô(xm,ym) phải bằng đường đi từ ô (M,N) đến ô (xm,ym) và giả sử bằng d , khi đó tổng độ dài đường đi từ ô (1,1) đến ô (M,N) sẽ bằng (2d - 1) là một số lẻ Như vậy, đây cũng là một hướng để giải quyết bài toán này, tức
là tìm đường đi độ dài lẻ ngắn nhất từ góc trái trên đến góc phải dướị Đó chính là con đường để hai robot gặp nhau mà số bước di chuyển cần thiết là ít nhất Tuy nhiên, tôi đã không tìm lời giải theo cách đó, phần này dành cho bạn đọc tự làm thêm ý tưởng loang theo từng lớp ở trên, không chỉ để giải bài toán “Meet” mà hy vọng rằng nó còn có tác dụng trong các ứng dụng khác của bạn