Thuật toán quay lui
Khử quay lui và bài toán từ đẹpLương Công HảoSau 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); … Từ 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; End; 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 If 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; end 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?) . 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.. Khử quay lui và bài toán từ đẹpLương Công HảoSau khi đọcxong bài "Một số bài toán tin khó" của thầy Nguyễn Xuân