Thuật toán Heuristic
Trang 1Tìm hiểu thuật toán Heuristic
Nguyễn Văn Sơn
Đối với nhiều bài toán, hoặc không có lời giải, hoặc độ phức tạp tính toán là hàm mũ, hoặc
là bài toán NP-đầy đủ…, có nghĩa là nó không có lời giải khả thi, thì thông thường thay vì tìm lời giải tối ưu cho chúng, chúng ta cố gắng tìm lời giải có thể chấp nhận được, đáp ứng được yêu cầu của thực tế Các lời giải này chính là các thuật toán heuristic
Các thuật toán tìm kiếm luôn luôn đóng vai trò quan trọng trong việc giải các bài toán tin học Các thuật toán loại này rất phong phú, có thể kể đến như: vét cạn, đệ quy quay lui, nhánh cận, nhị phân…Tuy nhiên, khi gặp những bài toán có không gian tìm kiếm lớn (đặc biệt trong các trò chơi cờ) thì các thuật toán tìm kiếm thông thường không cho kết quả hoặc kết quả không tối ưu (do những hạn chế về thời gian và bộ nhớ) Một hướng tiếp cận độc đáo có thể đáp ứng được đòi hỏi cho nhiều bài toán loại này là dùng thuật toán
Heuristic
Thuật ngữ Heuristic xuất phát từ tiếng Hy Lạp là ″heuriskein″ có nghĩa là ″tìm kiếm″ hoặc
″phát minh″ Chắc chắn chúng ta vẫn còn nhớ câu chuyện về nhà bác học Archimedes Khi phát hiện ra định luật về trọng lượng riêng, ông đã trần truồng chạy ra đường và kêu lớn
″tôi tìm ra rồi″ Thực ra, lúc đó ông đã kêu lên ″heureka″, về sau này người ta đổi từ này thành ″eureka″
Thuật ngữ Heuristic được Feigenbaum Feldman định nghĩa như sau: ″Heuristic là các quy tắc, phương pháp, chiến lược, mẹo giải hay phương cách nào đó nhằm làm giảm khối lượng tìm kiếm lời giải trong không gian bài toán cực lớn″
Tư tưởng chính để giảm khối lượng tìm kiếm là thay vì ″loại bỏ các hướng tìm kiếm chắc chắn không dẫn đến lời giải″, ta hãy ″chọn đi theo hướng có nhiều khả năng dẫn đến lời
giải″
Do thuật toán Heuristic được con người sử dụng thường mang đặc trưng của những gợi ý hay lời khuyên, nên các phương pháp dựa trên Heuristic đôi khi không chỉ ra con đường trực tiếp để đạt tới mục đích
Nhiều kết quả nghiên cứu trong trí tuệ nhân tạo cho thấy rằng, tuy có nhiều điểm mạnh nổi bật, nhưng trong một vài lĩnh vực nghiên cứu nào đó thì các phương pháp Heuristic còn
bộc lộ những điểm yếu nhất định Điều phức tạp chính là vì mọi chương trình máy tính
phải đảm bảo chắc chắn kết thúc công việc (theo định nghĩa một thuật toán phải đảm bảo tính đúng và tính dừng) Vì vậy, nếu nói rằng một chương trình nào đó sử dụng Heuristic thì kết luận về tính dừng chỉ đúng trong đa số các trường hợp Do đó, trong phần lớn trường hợp giải quyết bài toán, các chương trình Heuristic có lúc cho kết quả mong đợi, đôi khi lại không
Các Heuristic không chỉ tác động đến các chiến lược tìm kiếm, mà còn ảnh hưởng quyết
định tới các chiến lược điều khiển (hướng tìm kiếm trong không gian bài toán và xử lý
cạnh tranh) Đối với những bài toán trí tuệ phức tạp, số khả năng có thể lớn tới mức không thể có một máy tính nào dầu hiện đại đến mấy đáp ứng nổi Do vậy, thủ tục duyệt toàn thể không thể chấp nhận được Trong phần lớn các bài toán chứng minh định lý sử dụng logic,
số khả năng cần phải xét trở lên vô hạn Việc lựa chọn một nước đi tốt nhất trong trò chơi đòi hỏi phải tìm kiếm trong khoảng 1040 khả năng khác nhau, thậm chí đối với chơi cờ, số
Trang 2khả năng cỡ 10120 Một cách xử lý tốt trong những trường hợp này là sử dụng thao tác rút gọn các hướng tìm kiếm
Vấn đề quan trọng là ở chỗ, chúng ta biết khai thác khéo léo thông tin tại mỗi trạng thái để tìm ra thứ tự ưu tiên và đẩy nhanh quá trình tìm kiếm Thông thường ta gắn với mỗi trạng
thái của bài toán với một số đo (một hàm đánh giá) nào đó để đánh giá mức độ gần đích
của nó
Một kỹ thuật Heuristic được coi là hợp lý khi nó cho phép tiến hành đánh giá các khả năng để làm rõ khả năng nào tốt hơn các khả năng còn lại Những ví dụ điển hình về các
hàm đánh giá là các ước lượng khoảng cách đường chim bay từ một đỉnh nào tới đỉnh đỉnh đích trong bài toán xác định đường đi ngắn nhất, hay các đánh giá ước lượng mức độ quan trọng của các quân cờ trong mỗi thế cờ dựa trên tổ hợp các trọng số của chúng…
Để minh hoạ cho thuật toán Heuristic, chúng ta cùng nghiên cứu bài toán sau đây: bài toán
8-puzzle (đây là một trò chơi được một nhà thiết kế trò chơi nổi tiếng của Mỹ là Sam Loyd
phát minh vào cuối những năm 1870; nó đã trở thành một trò chơi được cực kỳ được ưa chuộng ở Mỹ vào thời điểm đó)
Bài toán:
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ố lên trên, xuống dưới, sang trái và sang phải từ một ô có con số sang ô trống (bài toán này còn có dạng phức tạp hơn là 15-puzzle)
Rõ ràng, đối với bài toán này nếu dùng phương pháp duyệt toàn bộ thì không hợp lý Để
giảm không gian tìm kiếm, ta xây dựng một thứ tự ưu tiên để duyệt các hướng đi Do đó,
khi duyệt thứ tự ưu tiên theo thứ tự giảm dần của khả năng dẫn đến lời giải, ta sẽ có cơ hội tiếp cận đến lời giải nhanh hơn là duyệt một cách mù quáng!
Tuy nhiên, điểm mấu chốt là: làm thế nào để đánh giá khả năng dẫn đến lời giải của một hướng đi? Nếu đánh giá này chính xác, chúng ta sẽ tìm thấy lời giải nhanh hơn còn nếu không thì trong trường hợp xấu nhất, lại trở về vét cạn toàn bộ
Trong bài toán này, ta sử dụng hàm đánh giá ký hiệu là h với ý nghĩa: h(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 Khi đó, trạng thái có tiềm năng dẫn tới đích nhanh nhất là trạng thái có hàm đánh giá h đạt giá trị min
Thuật toán cho trò chơi 8-puzzle:
Một số ký hiệu
- Tập P các trạng thái chờ quyết định được tổ chức dưới dạng stack
- Tập Q các trạng thái đã phát triển được tổ chức dưới dạng hàng đợi
- Tập P′ lưu trữ tạm thời các trạng thái kề v của u khi ta phát triển một đỉnh u (v thu được
từ u thông qua một phép di chuyển con số)
- Biến found
Trang 3- Hàm father.
Thuật toán:
Input: trạng thái ban đầu u0 và trạng thái đích ut
1.P← { u0 } ; Q ← Φ ; found ← false; P′ ← Φ ;
2.While(P ≠ Φ ) and (not found) do
2.1 Loại trạng thái u ở đỉnh stack P và đặt nó vào Q:
Pop(P,u);
Ađ(u,Q);
2.2 If u=ut then found ← true
Else for v thuộc kề(u) do
If v không thuộc P hợp Q then
Begin
father (v) ← u;
Ađ (v,P′);
End;
If P′ ≠ Φ then
Begin
Sắp xếp P′ theo thứ tự tăng của hàm h Chuyển P′ vào đỉnh stack P sao cho trạng thái nhiều hứa hẹn nhất (có giá trị hàm h nhỏ nhất) ở đỉnh của P
End;
(Chú ý: Sau khi chuyển P′ vào đỉnh stack P thì P′ trở thành rỗng.)
Output: Nếu found = false thì bài toán vô nghiệm.
Nếu found = true thì nghiệm là đường đi từ u0 tới ut được xác định bằng cách sử dụng hàm father
Cây tìm kiếm được hình thành do sử dụng thuật toán Heuristic ở trên được minh hoạ như trong hình vẽ dưới (nét đậm thể hướng tìm kiếm), số ghi cạnh các đỉnh là giá trị của hàm h tại đỉnh đó
Trang 4Minh hoạ cây tìm kiếm cho trò chơi 8-puzzle bằng thuật toán Heuristic
Chương trình minh hoạ cho thuật toán Heuristic giải quyết bài toán ở dạng tổng quát được viết bằng ngôn ngữ Pascal như dưới đây
Dữ liệu vào cho trong file SO.INP: dòng đầu là một số n (1<N2-1)-puzzle) Tiếp theo là hai nhóm dòng, mỗi nhóm gồm n dòng (gồm các số được ghi cách nhau một dấu cách) lần lượt minh hoạ cho bảng số nguồn và bảng số đích Dữ liệu ra được ghi vào file SO.OUT gồm nhiều nhóm dòng, mỗi nhóm gồm n+1 dòng: bắt đầu là một số chỉ thứ tự của bảng số được phát triển tính từ bảng số nguồn và n dòng tiếp theo minh hoạ cho bảng số đó Nếu không tồn tại lời giải thì ghi một số 0
Ví dụ:
SO.INP
3
2 6 3
1 6 4
7 0 5
1 2 3
8 0 4
7 6 5
SO.OUT
1
2 8 3
1 4
7 6 5
2
2 3
1 8 4
7 6 5
Trang 52 3
1 8 4
7 6 5
4
1 2 3
8 4
7 6 5
5
1 2 3
8 4
6 5
Sau đây là toàn văn chương trình:
PROGRAM heuristic;
{$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;
Trang 6PROCEDURE 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; Ktra:=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
Trang 7close(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 Uo: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(Uo);
Uô.infor.ma:=Dau;Uô.infor.father:=0;
Uô.infor.child:=1;Uô.next:=nil;
Top:=Uo;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;
Trang 8If 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);
Trang 9End; 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
Chú ý: Việc lựa chọn hàm đánh giá cho bài toán trên có thể tính thông qua tổng khoảng
cách ngắn nhất (tính theo ô) của từng con số trong trạng thái hiện tại so với vị trí đúng của
nó trong trạng thái đích
Một ví dụ khác là bài toán tô màu cho đồ thị Giả sử G = là một đồ thị không định hướng
Yêu cầu tô màu cho tất cả các đỉnh của G sao cho hai đỉnh được nối bằng một cung thì phải có hai màu khác nhau Có thể thấy rằng bài toán tô màu này sao cho số màu được dùng là ít nhất là một bài toán NP-đầy đủ Lúc này, ta tìm một lời giải tốt có thể chấp nhận được theo một thuật toán heuristic như sau:
Ban đầu chọn một màu và một đỉnh xuất phát Ta tô màu đỉnh này và tất cả các đỉnh khác
có thể tô được mà vẫn thoả mãn điều kiện của bài toán Chúng ta chọn màu mới và một đỉnh xuất phát mới (chưa được tô), ta tô đỉnh này và tất cả các đỉnh khác (còn chưa được tô) có thể tô được bằng màu thứ hai, và cứ như vậy…cho đến khi tô hết các đỉnh của G thì thôi
Bài tập 1: Các ô vuông thần bí (đề thi Olympic tin học quốc tế 1996)
Kế tục thành công của trò chơi với khối lập phương thần bí, Ngài Rubik sáng tạo ra dạng phẳng của trò chơi này gọi là trò chơi các ô vuông thần bí Đây là một bảng gồm 8 ô vuông bằng nhau (xem Hình 1)
Chúng ta xét bảng trong đó mỗi ô vuông có một màu khác nhau Các màu được ký hiệu bởi tám số nguyên dương đầu tiên (xem Hình 1) Trạng thái của bảng được cho bởi dãy ký hiệu màu của các ô được viết lần lượt theo chiều kim đồng hồ bắt đầu bởi ô ở góc trái trên
và kết thúc tại ô ở góc trái dưới Ví dụ, trạng thái của bảng trong hình 1 được cho bởi dãy (1, 2, 3, 4, 5, 6, 7, 8) Trạng thái này được gọi là trạng thái khởi đầu (hay trạng thái đích)
Có thể dùng 3 phép biến đổi cơ bản đối với bảng có tên là′A′, ′B′, ′C′:
′A′: đổi chỗ dòng trên và dòng dưới Ví dụ sau phép biến đổi ′A′thì hình 1 thành:
Trang 10′B′: thực hiện một phép hoán vị vòng quanh sang phải Ví dụ sau phép biến đổi ′B′ thì hình
1 thành:
′C′: quay theo chiều kim đồng hồ bốn ô giữa Ví dụ sau phép biến đổi ′C′ thì hình 1 thành:
Biết rằng từ một trạng thái khởi đầu luôn có thể chuyển về một trạng thái bất kỳ bằng cách dùng các phép biến đổi cơ bản nói trên
Hãy viết chương trình tìm các phép biến đổi cơ bản để chuyển bảng từ trạng thái khởi đầu cho trong hình 1 về một trong các trạng thái cho trước (Câu A), bạn sẽ được thêm hai điểm nếu số lượng phép biến đổi tìm được không vượt quá 300 (Câu B)
Dữ liệu vào: file INPUT.TXT chứa 8 số nguyên dương trong dòng đầu tiên mô tả trạng
thái đích
Dữ liệu ra: trong dòng đầu tiên của file OUTPUT.TXT chứa một số L là số lượng phép
biến đổi của dãy các phép biến đổi tìm được Trong L dòng tiếp theo phải ghi dãy tên các phép biến đổi cơ bản theo trình tự thực hiện, mỗi tên ghi ở vị trí đầu tiên của mỗi dòng
Ví dụ về dữ liệu vào và dữ liệu ra:
Bài tập 2: (Bài toán người bán hàng)
Người bán hàng có hành trình sao cho mỗi thành phố đi đến đúng một lần và quay trở lại thành phố xuất phát Hãy tìm một hành trình đi ngắn nhất cho người bán hàng đó
Thuật toán chúng ta vừa nghiên cứu ở trên đã chứng tỏ sự hiệu quả của nó, bởi lẽ thực tiễn ngày nay đã chỉ ra rằng đi đôi với sự phát triển của khoa học và công nghệ thì chúng ta ngày càng gặp nhiều bài toán mà đối với chúng rất khó xác định có tồn tại hay không một lời giải chính xác Sử dụng thuật toán này cùng với một vài kỹ năng lập trình, bạn đọc có thể dễ dàng viết một vài chương trình trò chơi đơn giản