Thuật toán quay lui
Trang 1Khử quay lui và bài toán từ đẹp
Lương Công Hảo
Sau khi đọcxong bài "Một số bài toán tin khó" của thầy Nguyễn Xuân Huy và thầy Hồ Sỹ
Đàm và các số báo của TH&NT viết về Giải Thuật Quay lui và khử Quaylui của các tác
giả: Chu Đức Minh, Nguyễn Văn Trường, Nguyễn Duy Hàm,Trương Thị Thu Hường…đã
nói rất rõ về giải thuật này Song đểvận dụng và giải quyết một số bài toán (như bài toán
"Từ đẹp" ) thì quả thật là khó khăn Nhằmgiúp các em học sinh hiểu thấu đáo hơn, tôi xin
mạnh dạn đưa ra cácý kiến sau mong các em học sinh và các bạn đồng nghiệp tham khảo gópý
Xuất pháttừ bài toán: Viết tất cả các dãy có độ dài n từ các ký tự A,B, C(chẳng hạn với
n=3, ta có: AAA, AAB, AAC, ) Để giải quyết bài toánnày ta chỉ cần dùng 3 vòng lặp For(n=3) là được Song với n lớn thì sao? Nếu giải quyết bài toán nàybằng quay lui, ta có thủ tục sau:
hellip;
m=’ABC’
hellip;
proceduretry(i:integer);
varj:integer;
begin
for j:=1 to3 do
begin
x[i]:=m[j];
if i=n theninkq esle try(i+1);
end;
…
try(1);
…
Trang 2Từ mô hìnhtrên ta thấy:
- Khi gọi Try(i+1) mức tănglên 1
- Khi lùi mức giảm 1
- Và giảm đến mức 1
Như vậy,để tránh việc tràn Stack, ta có thể khử quay lui nếu biết tổ chức vàlưulại các giá trị đề cử j Để lưu j ứng với các mức của i ta dùngmảng a: array[1 n+1] of byte với a[i]=i (tại mỗi thời điểm) (xuất pháta[i]=1, i=1 n) Sau mỗi lần đề cử được tăng j ta tăng
a[i]:=a[i]+1, nếua[i] vượt quá giá trị đề cử ta gán a[i]:=1 Việc thay đổi này cóthể đặt trong hàm Đề cử (decu) hoặc trong thủ tục khử quay lui(khu_try)
Trong bàitoán này, hàm decu rất đơn gảin vì các khả năng của j đều chấp nhậnđược vì không có ràng buộc nào Để lưu nghiệm ta dùng biến string(nhược điểm: dài tối đa 256)
Sau đây là văn bản chươngtrình:
Programkhu_quay_lui;
Const n=3;
Varx,y:string ;
A:array[1 300] of byte;
I:integer;
Procedureinkq;
Begin
Writeln(x);
End;
Functiondecu(i:integer):integer;
Varj:integer;
Begin
Decu:=0;
For j:=a[i]to 3 do
Begin
Decu:=j;
Exit;
End;
End;
Procedurekhu_try;
Varj:integer;
Stop:boolean;
Begin
Stop: false;
While notstop do
Begin
Repeat
J:=decu(i);
If j>0then
Begin
X:=x+y[j];
I:=i+1;
If i>nthen begin inkq; j:=0; end;
If a[i]>3then a[i]:=1;
Trang 3End;
Until j=0;
I:=i-1;
If i>0then begin a[i]: a[i] +1; delete(x,i,1);
End elsestop:=true;
End;
End;
Begin
Y:=’ABC’;
For i:=1 ton do a[i]:=1;
I:=1;
X:=’’;
Khu_try;
Readln;
End
Từ bài toántrên ta có thể phát triển để giải quyết bài "Từ đẹp" (Bài 3a − Tìm chuỗi có độ dài n xây dựng từ các ký tự A,B,Csao cho không có 2 chuỗi con liên tiếp giống nhau), với
các bổ xungsau:
- Viết thêm hàm KT: kiểmtra xem chuỗi có 2 chuỗi con liên tiếp hay không?
- Dùng mảng x: array[1 n]of char để lưu kết quả (để có độ dài lớn)
- Không duyệt tất cả cácdãy mà chỉ cần tìm một nghiệm đầu tiên (đặt exit trong lệnh kiểmtra đủ cấu hình)
Do chỉ tìmmột nghiệm đầu tiên nên chương trình chạy tương đối nhanh, vớin=700 khoảng
1 giây, với n=10000 khoảng 2 phút (nếu dùng quay lui chỉ chạyđược với n≤29, nếu có dẫn biên dịch {$M65520,0,655360} thì n≤123)
Sau đây là văn bản chươngtrình:
ProgramKhu_quay_lui_bai_tu_dep;
{$R-}
const n =10000;
typechuoi=array[1 10001] of char;
var
a:array[1 10001]of byte;
c,y: chuoi;
m:string[3];
i:integer;
functionkt(l:integer):boolean;
vart,k,m,d,c,c1:integer;
begin
kt:=true;
for t:=1 totrunc(1/2) do
begin
d:=1-2*t+1;
c:=1-t+1;
c1:=c;
m:=0;
Repeat
Trang 4If y[d]=y[c]then m:=m+1;
D:=d+1;c:=c+1;
If m=t thenbegin kt:=false; exit; end;
Until d=c1;
End;
End;
Functiondecu(k:integer):integer;
Varz:integer;
Begin
Decu:=0;
For z=1 to ndo y[z]:=��;
For z:=1 tok-1 do y[z]:= c[z];
For z:=a[k]to 3 do
Begin
Y[k]:=m[z];
A[k]:=a[k]+1;
If kt(k)then
Begin
Decu:=z;
Exit;
End;
End;
End;
Procedureinkq(var q:chuoi);
Varj:integer;
Begin
For j:=1 ton do write(q[j]);
Writeln;
End;
Proceduretry;
Varj,k:integer;
Begin
Repeat
Repeat
J:= decu(j);
If j>0then {chap nhan j}
Begin
C[i]:=m[i];
If (i=n)then
Begin
Exit; {thoatkhong ghi nhan duoc nghiem dau tien} {inkq(c);
if a[i]>3then begin a[i]:=1;j:=0; end;}
end else
i:=i+1;
if (i>n)then j:=0;
Trang 5end
else ifa[i]>3 then begin a[i]:=1; end;
until (j=0);
i:=i-1;
if (i>0)then
begin
if(a[i]>3) then begin a[i]:=1;i:=i-1; end;
end;
until i=0;
end;
Begin
Fillchar(c,sizeof(c),’’);
For i:=1 ton do a[i]:=1;
M:=’ABC’;
I:=1;
Try;
Writeln(’Chuoican tim la:’);
Inkq(c);
Readln;
End
*) Rõ ràng tađã biết, với 2 chữ cái thì bài toán "Từ đẹp " sẽ vô nghiệm khin ≥4 Vì với n=3
ta chỉ có 2 nghiệm là ABA và BAB, do chỉ có 2 khảnăng đề cử là A hoặc B nên không thể thêm vào được nữa để sinhchuỗi có độ dài lớn hơn 3 (thêm vào sẽ vi phạm luật "đẹp
"ngay)
Với 3 chữ cái thì bài toán có nghiệm với mọi nnguyên dương Vì có thể dựa vào 2 yều tố sau:
Theokết quả từ chương trình, khi n càng lớn thì số từ đẹp càng tăngnhanh Ví dụ: n=2 (có 6 nghiệm), n=3 (có 12 nghiệm), n=4 (có 18 nghiệm),n=5 (có 30 nghiệm), n=6 (có 42
nghiệm), n=7 (có 60 nghiệm), n=8 (có 78nghiệm), n=9 (có 108 nghiệm),&hưellip; Giảsử ta
đã xây dựng được từ đẹp có độ dài n≥2 - Nếu n chẵn thì bao giờcũng thêm được một ký tự (khác với ký tự cuỗi hoặc áp cuỗi) vàosau để được từ đẹp có độ dài n+1
- Những từ đẹp có độdài n+1 (lẻ) nếu không phải dạng "đối xứng gánh " cũng đều cóthể thêm vào ký tự cuỗi để được từ đẹp mới
Ví dụ từ đẹp "đối xứng gánh ": ACABACA khôngthể thêm vào 1 ký tự nào nữa, nhưng không phải tất cả các nghiệmđều có dạng này
*) Nếu thêm ràngbuộc "Bài 3b − cố định một chữ cái x, tìm từ đẹp có chiều dài n
saocho số lần xuất hiện của ký tự x là ít nhất " thì ta phải duyệt tất cả các từ đẹp và với
mỗi nghiệm ta đếmsố ký tự x (đếm khi xây dựng nghiệm), sau đó dùng thêm thủ
tụcTimKetQua để ghi nhận kỷ lục (số lần xuất hiện ký tự ít nhất)
*) Về nhậnđịnh "Nếu bài toán 3a giải được thì bài toán 3b cũng giải đượcvà ngược lại ", theo tôi nhận xét như sau: Về mặt lý thuyết thì hiểnnhiên nhận định trên đúng nhưng thực
tế chỉ đúng với n nhỏ, cònn lớn thì bài toán 3b không thể giải quyết được (với n
khoảng10000?)