5.2.1. Duyệt toàn bộ
Một trong những phương phỏp hiển nhiờn để giải bài toỏn tối ưu tổ hợp đặt ra là: Trờn cơ sở cỏc thuật toỏn liệt kờ tổ hợp ta tiến hành duyệt cỏc ph- ương
ỏn của bài toỏn, đối với mỗi phương ỏn ta đều tớnh giỏ trị hàm mục tiờu tại nú, sau đú so sỏnh giỏ trị hàm mục tiờu tại tất cả cỏc phương ỏn được liệt kờ để tỡm ra phương ỏn tối u. Phương phỏp xõy dựng theo nguyờn tắc nhưvậy cú tờn gọi là phương phỏp duyệt toàn bộ. Duyệt toàn bộ là khú cú thể thực hiện được ngay cả trờn mỏy tớnh điện tử hiện đại nhất. Vớ dụ để liệt kờ hết
15! = 1.307.674.368.000
hoỏn vị trờn mỏy tớnh điện tử với tốc độ tớnh toỏn 1 tỷ phộp tớnh một giõy, nếu để liệt kờ một hoỏn vị cần phải làm 100 phộp tớnh, thỡ ta cần một khoảng thời gian là 130767 giõy > 36 tiếng đồng hồ! Vỡ vậy cần phải cú những biện phỏp nhằm hạn chế việc tỡm kiếm thỡ mới cú hy vọng giải được cỏc bài toỏn tối ưu tổ hợp thực tế. Tất nhiờn để cú thể đề ra những biện phỏp như vậy cần phải nghiờn cứu kỹ tớnh chất của bài toỏn tối ưu tổ hợp cụ thể. Nhờ những nghiờn cứu như vậy, trong một số trường hợp cụ thể ta cú thể xõy dựng những thuật toỏn hiệu quả để giải bài toỏn đặt ra. Tuy nhiờn phải nhấn mạnh rằng trong nhiều trường hợp (Chẳng hạn trong cỏc bài toỏn người du lịch, bài toỏn cỏi tỳi, bài toỏn cho thuờ mỏy nờu ở trờn) chỳng ta chưa thể xõy dựng được phương phỏp hữu hiệu nào khỏc ngoài phương phỏp duyệt toàn bộ. Khi đú, một vấn đề đặt ra là trong quỏ trỡnh liệt kờ lời giải ta cần tận dụng cỏc thụng tin đó tỡm được để loại bỏ những phương ỏn chắc chắn khụng phải là tối ưu.
5.2.2. Thuật toỏn nhỏnh cận
Giới thiệu thuật toỏn. Ta sẽ mụ tả tư tưởng của bài toỏn trờn mụ hỡnh bài toỏn tối ưu tổ hợp tổng quỏt sau
min { f(x): xD}
trong đú Dlà tập hữu hạn cỏc phần tử. Giả thiết D được mụ tả như sau:
D = { x = (x1, x2,…, xn) A1 A2…An: x thỏa món tớnh chất P}, với A1, A2,…An là cỏc tập hữu hạn, cũn P là tớnh chất cho trờn tớch Đềcac A1
A2…An.
Nhận thấy rằng cỏc bài toỏn đó trỡnh bày ở mục trước đều cú thể mụ tả dưới dạng bài toỏn trờn.
Với giả thiết về tập D nờu trờn, chỳng ta cú thể sử dụng thuật toỏn quay lui để liệt kờ cỏc phương ỏn của bài toỏn. Trong quỏ trỡnh liệt kờ theo thuật toỏn quay lui, ta sẽ xõy dựng dần cỏc thành phần của phương ỏn. Một bộ gồm k
thành phần (a1, a2,…, ak) xuất hiện trong quỏ trỡnh thực hiện thuật toỏn sẽ gọi là phương ỏn bộ phận cấp k.
Thuật toỏn nhỏnh cận cú thể ỏp dụng để giải bài toỏn đặt ra nếu như cú thể tỡm được một hàm g xỏc định trờn tập tất cả cỏc phương ỏn bộ phận của bài toỏn thỏa món bất đẳng thức sau:
g(a1, a2,…, ak) min{f(x): xD, xi =ai, i= 1,2,…, k } (*) với mọi lời giải bộ phận ( a1,a2,…, ak ), và với mọi k= 1,2,…
Bất đẳng thức (*) cú nghĩa là giỏ trị của hàng g tại phuơng ỏn bộ phận (a1, a2,…, ak ) là khụng vượt quỏ giỏ trị nhỏ nhất của hàm mục tiờu của bài toỏn trờn tập con cỏc phương ỏn
D(a1, a2,…, ak ) = { x D :xi= ai , i = 1, 2,…, k}
hay núi một cỏch khỏc, g(a1, a2, …, ak ) là cận dưới của giỏ trị hàm mục tiờu trờn tập D(a1, a2,…, ak). Vỡ lẽ đú, hàm g được gọi là hàm cận dưới , và giỏ trị g(a1, a2, …, ak) được gọi là cận dưới của tập D(a1, a2,…, ak). Do cú thể đồng nhất tập D(a1, a2,…,ak) với phương ỏn bộ phận (a1, a2,…, ak), nờn ta cũng gọi giỏ trị g(a1, a2, …, ak) là cận dưới của phương ỏn bộ phận (a1, a2, …, ak).
Giả sử đó cú hàm g. Ta xột cỏch sử dụng hàm này để giảm bớt khối lượng duyệt trong quỏ trỡnh duyệt tất cả cỏc phương ỏn theo thuật toỏn quay lui. Trong quỏ trỡnh liệt kờ cỏc phương ỏn cú thể đó thu được một số phương ỏn của bài toỏn. Gọi
x là phương ỏn với giỏ trị hàm mục tiờu nhỏ nhất trong số cỏc phương đó tỡm được, kớ hiệu f = f(x ). Ta sẽ gọi x là phương ỏn tốt nhất hiện cú, cũn f là kỷ lục. Giả sử ta đó cú f . Khi đú nếu g(a1, a2, …, ak) > f , thỡ từ bất đẳng thức (*) suy ra f < g(a1, a2, …, ak) min {f(x): xD, xi =ai, i= 1,2,…, k },
vỡ thế tập con cỏc phương ỏn của bà toỏn D(a1, a2,…,ak) chắc chắn khụng chứa phương ỏn tối u. Trong trường hợp này ta khụng cần tiếp tục phỏt triển phư- ơng ỏn bộ phận (a1, a2,…,ak), núi cỏch khỏc là ta cú thể loại bỏ cỏc phương ỏn trong tập D(a1, a2,…,ak)khỏi quỏ trỡnh tỡm kiếm.
Procedure Try(k);
(* Phỏt triển phương ỏn bộ phận (a1, a2,…,ak-1) theo thuật toỏn quay lui cú kiểm tra cận dưới trước khi tiếptục phỏt triển phương ỏn *)
begin for ak Ak do if <chấp nhận ak> then begin xk := ak; if k = n then <cập nhật kỷ lục> else if g((a1, a2,…,ak) f then Try(k+1) end; end;
Khi đú, thuật toỏn nhỏnh cận được thực hiện nhờ thủ tục sau:
Procedure Nhỏnh_cận;
begin
f :=;
(* Nếu biết một phương ỏn
xnào đú ta cú thể đặt
f = f(x) *). Try(1);
if f < then < f là giỏ trị tối ưu, x là phương ỏn tối u > else < bài toỏn khụng cú phương ỏn>;
end;
Chỳ ý rằng nếu trong thủ tục Try ta thay cõu lệnh
if k=n then < cập nhật kỷ lục> else if g(a1, a2,…, ak) f then Try (k+1) bởi if k= n then < cập nhật kỷ lục> else Try(k+1)
thỡ thủ tục Try sẽ liệt kờ toàn bộ cỏc phương ỏn của bài toỏn, và ta thu đựơc thuật toỏn duyệt toàn bộ.Việc xõy dựng hàm g phụ thuộc vào từng bài toỏn tối ưu tổ hợp cụ thể. Thụng thường ta cố gắng xõy dựng nú sao cho:
Việc tớnh giỏ trị của g phải đơn giản hơn việc giải bài toỏn tối ưu tổ hợp ở vế phải cuả (*).
Giỏ trị của g( a1, a2,…, ak)phải sỏt với giỏ trị của vế phải của (*). Tuy nhiờn hai yờu cầu này trong thực tế thường đối lập nhau.
Bài toỏn ngƣời du lịch
Mụ hỡnh của bài toỏn đó trỡnh bày trong mục trước. Cố định thành phố xuất phỏt là T1,bài toỏn người du lịch dẫn về bài toỏn:
Tỡm cực tiểu của hàm
f( x2, x3,…,xn) = c[1, x2] + c[ x2, x3] +…+ c[ xn-1 + xn] + c[ xn, 1] min Với điều kiện ( x2, x3,…,xn) là hoỏn vị của cỏc số 2, 3,…, n.
Ký hiệu Cmin = min{c[ i, j ], i, j = 1,2,…,n, i j }
là chi phớ đi lại nhỏ nhất giữa cỏc thành phố.
Giả sử ta đang cú phương ỏn bộ phận (u1, u2,…,un). phương ỏn này tương ứng với hành trỡnh bộ phận qua k thành phố:
T1T(u2) ...T(uk-1)) T(uk). Vỡ vậy, chi phớ phải trả theo hành trỡnh bộ phận này sẽ là
= c[1,u2] + c[u2,u3] + …+ c[uk-1,uk].
Để phỏt triển hành trỡnh bộ phận này thành hành trỡnh đầy đủ, ta cũn phải đi qua n-k thành phố cũn lại rồi quay trở về thành phố T1, tức là cũn phải đi qua n-k+1 đoạn đường nữa.
Do chi phớ phải trả cho việc đi qua mỗi một trong số n-k+1 đoạn đường cũn lại đều khụng ớt hơn cmin nờn cận dưới cho phương ỏn bộ phận (u1,u2,…,uk) cú thể tớnh theo cụng thức
Vớ dụ 5.6. Giải bài toỏn người du lịch theo thuật toỏn trỡnh bày trờn với ma trận chi phớ: 0 3 14 18 15 3 0 4 22 20 C = 17 9 0 16 4 6 5 7 0 12 9 15 11 5 0
Ta cú cmin = 3. Quỏ trỡnh thực hiện thật toỏn được mụ tả bởi cõy tỡm kiếm lời giải cho trong hỡnh dưới.
f = + ∞ (2) =3 g=15 (3) = 14 g= 16 (4) = 18 g = 30 (5) = 15 g = 27 (2,3) = 7 g = 16 (2,4) = 25 g = 34 (2,5) = 23 g =32 (2,3,4) = 23 g = 29 (2,3,5) = 11 g = 17 (2,3,4,5) = 35 g = 38 (2,3,5,4) = 16 g = 19 Các nhánh này bị loại vì có cận d- ới g > f = 22 Hành trình (1,2,3,4,5,1) Chi phí là 44. Đặt f =44 Hành trình (1,2,3,5,4,1) Chi phí là 22. Đặt f =22
Thông tin về một ph- ơng án bộ phận trên cây đ- ợc ghi trong các ô ở trên hình vẽ tuơng ứng theo thứ tự sau: Đầu tiên là các thành phần của ph- ơng án, tiếp đến là chi phí theo hành trình bộ phận và g – cận duới.
Kết thúc thuật toán , ta thu đựơc ph- ơng án tối - u (1,2,3,5,4,1) tuơng ứng với hành trình
T1 -> T2 -> T3 -> T4 ->T5->T1 với chỉ nhỏ nhất là 22.
Chuơng trình trên PASCAL thực hiện thuật toán có thể viết nh sau: Program nhanhcan;
Uses crt. Var
c:array[1..20,1..20] of integer;
a, xopt :array[1..20,1..20] of integer; chuaxet :array[1..20,1..20] of integer; n,fopt, cmin, can : integer;
procedure Readfile; var f: text;
name : string; i, j : integer; begin
write(‘cho ten file du lieu: ‘); readln(name);
assign(f, name); reset(f); read(f, n); for i:=1 to n do for j:=1 to n do read(f,c[i,j]); close(f); cmin:= maxint; for i:=1 to n do for j: =1 to n do
if (i<>j) and (cmin > c[i.j]) then cmin = c[I,j]; end;
procedure Ghinhan; var sum : integer;
begin
sum := can +c[a[n],a[1]]; if sum < fopt then
begin xopt: = a; fopt: = sum; end; end; procedure Try(i:integer); var j :integer; begin
(*co dinh thanh pho xuat phat la thanh pho 1 *); (* duyet (n-1)! Hanh trinh theo nhanh can *) For j:=2 to n do
If chuaxet[j] then Begin
a[i]:=j;
chuaxet[j]:=false;
can:= can + c[a[i-1], a[i]]; if i=n then ghinhan
else
if can+(n-i+1)* cmin < fopt then try(i+1); can := can – c[a[i-1],a[i]];
chuaxet(j):= true; end;
end;
procedure Inkq; var I,j: integer; begin
writeln(‘MA TRAN CHI PHI ‘); for i:=1 to n do
begin
for j:=1 to n do write(c[I,j]:4); writeln;
writeln(‘hanh trinh to uu co chi phi: ‘,fopt); for i:=1 to n do write(fopt(i),’’);
writeln(xopt[1]);
write(‘go enter de tiep tuc…’);readln; end;
procedure Init; var I,j: integer; begin cmin:= maxint; for i:=1 to n do begin chuaxet[i]:= true; for j:=1 to n do
if(i<>j) and (cmin > c[I,j] ) then cmin:=c[I,j]; end; fopt:= maxint; can:=0; a[1]:= 1; end; BEGIN Readfile; Init; Try(2); Inkq; END.
Hiệu quả của thuật toán nhánh cận phụ thuộc rất nhiều vào việc xây dựng hàm tính cận d- ới. Việc xây dựng hàm tính cận d- ới lại phụ thuộc vào cách xây dựng thủ tục duyệt các ph- ơng án của bài toán (đ- ợc gọi là thủ tuc phân nhánh).
Trên đây chúng ta trình bày cách xây dựng cận d- ới khá đơn giản cho bài toán ng- ời du lịch. Ch- ơng trình đ- ợc cài đặt theo các thuật toán đó, tuy rằng làm việc tốt hơn nhiều so với duyệt toàn bộ, nh- ng cũng chỉ có thể áp dụng để giải các bài toán với kích th- ớc nhỏ. Muốn giải đ- ợc các bài toán đặt ra với kích th- ớc lớn hơn cần có cách đánh giá cận tốt hơn. Một trong những
ph- ơng pháp xây dựng trên t- t- ởng của thuật toán nhánh cận cho phép giải bài toán ng- ời du lịch với kích th- ớc lớn hơn sẽ đ- ợc trình bày d- ới đây.
5.3. Thuật toán nhánh cận giải bài toán ng- ời du lịch
Thuật toán nhánh cận là một trong những phuơng pháp giải chủ yếu của tối - u tổ hợp. Nh- trong mục tr- ớc đã thấy t- t- ởng cơ bản của nó là trong quá trình tìm kiếm lời giả ta sẽ phân hoạch tập các ph- ơng án của bài toán ra thành 2 hay nhiều tập con đ- ợc biểu diễn nh- là các nút của cây tìm kiếm và cố gắng bằng phép đánh giá cận cho các nút, tìm cách loại bỏ những nhánh cây tìm kiếm (những tập con các ph- ơng án của bài toán) mà ta biết chắc chắn là không chứa ph- ơng án tối - u. Mặc dù trong tình huống tồi nhất thuật toán sẽ trở thành duyệt toàn bộ, nh- ng trong nhiều tr- ờng hợp cụ thể, kĩ thuật đó cho phép rút ngắn đ- ợc một cách đáng kể quá trình tìm kiếm. Mục này sẽ trình bày một cách thể hiện khác những t- t- ởng của thuật toán nhánh cận vào việc xây dựng thuật toán giải bài toán ngời du lịch.
Xét bài toán ng- ời du lịch phát biểu trong mục tr- ớc. Gọi C = {cij: i,j = 1,2,…,n}
là ma trận chi phí. Mỗi hành trình của ng- ời du lịch T(1) , T(2),.. , T(n), T(1)
có thể là viết lại d- ới dạng
((1), (2)), ((2), ((3)),… ((n-1), ((n)), ((n), ((1)),
Trong đó mỗi thành phần ((j-1), ((j)) sẽ đ- ợc gọi là một cạnh của hành trình.
Trong bài toán ng- ời du lịch khi tiến hành tìm kiếm lời giải chúng ta sẽ phân tập các hành trình ra thành hai tập con: một tập gồm những hành trình chứa một cạnh(i,j) nào đó còn tập kia gồm những hành trình không chứa cạnh này. Ta gọi việc làm đó là phân nhánh và mỗi tập con nói trên sẽ đ- ợc gọi là nhánh hay một nút tìm kiếm. Việc phân nhánh đ- ợc minh hoạ bởi cây tìm
Việc phân nhánh sẽ đ- ợc thực hiện trên một quy tắc Ơristic nào đó cho phép ta rút ngắn quá trình tìm kiếm ph- ơng án tối - u. Sau khi phân nhánh chúng ta sẽ tính cận d- ới của giá trị hàm mục tiêu trên mỗi một trong hai tập con nói trên. Việc tìm kiếm sẽ đ- ợc tiếp tục trên tập con có giá trị cận d- ới nhỏ hơn. Thủ tục này sẽ đ- ợc tiếp tục cho đến khi thu đ- ơc một hành trình đầy đủ, tức là một ph- ơng án của bài toán ng- ời du lịch. Khi đó ta chỉ cần xét những tập con các ph- ơng án nào có cận d- ới nhỏ hơn giá trị mục tiêu tại ph- ơng án đã tìm đ- ợc. Quá trình phân nhánh và tính cận trên tập các ph- ơng án của bài toán thông th- ờng cho phép rút ngắn một cách đáng kể quá trình tìm kiếm do ta loại đ- ợc khá nhiều tập con chắc chắn không chứa ph- ơng án tối - u.
5.3.1. Thủ tục rút gọn (tính chi phí cận d- ới)
Rõ ràng tổng chi phí của một hành trình của ng- ời du lịch sẽ chứa đúng một phần tử của mỗi dòng và một phần tử của mỗi cột trong ma trận chi phí C Do đó, nếu ta trừ bớt mỗi phần tử của một dòng (hay một cột) của ma trận C đi cùng một số thì độ dài của tất cả các hành trình sẽ cùng giảm đi . Vì thế hành trình tối - u cũng sẽ không thay đổi. Vì vậy nếu ta tiến hành trừ bớt các phần tử của mỗi dòng và mỗi cột đi một hằng số sao cho thu đ- ợc ma trận gồm các phần tử không âm mà trong mỗi dòng và mỗi cột của nó đều có ít nhất một số không thì tổng các hằng số trừ đó sẽ cho ta cận d- ới của mọi hành trình. Thủ tục trừ bớt này sẽ đợc gọi là thủ tục rút gọn. Các hằng số trừ ở mỗi dòng (cột) sẽ đ- ợc gọi là hằng số rút gọn theo mỗi dòng (cột) còn ma trận thu đ- ợc sẽ gọi là ma trận rút gọn.
hành trình chứa (i,j)
Tập tất cả các hành trình
hành trình không chứa (i,j)
Function Reduce(A,k):real;
Begin
sum:= 0
for i:=1 to k do (*k-kích th- ớc của A *) Begin
r[i]:= <phần tử nhỏ nhất trong dòng i> if r[i]>0 then
begin
<bớt mỗi phần tử của dòng i đi r[i] >; Sum := sum +r[i];
end; End; for j:=1 to k do Begin s[j] :=<phần tử nhỏ nhất trong cột j >; if s[j] >0 then begin
<bớt mỗi phần tử của cột j đi s[j] > ; sum := sum +s[j] ;
end; End;
Reduce := sum; End;
Ví dụ 5.7. Ta có ma trận chi phí của bài toán ng- ời du lịch với n= 6 thành phố
sau :
Đầu tiên trừ bớt mỗi phần tử của các dòng 1, 2,3 , 4, 5, 6 đi các hằng số rút gọn t- ơng ứng là 3, 4, 16, 7, 25, 3, sau đó trong ma trận thu đ- ợc, trừ bớt các phần tử của các cột 3 và 4 cho các hằng số rút gọn t- ơng ứng là 15 và 8, ta thu đ- ợc ma trận rút gọn sau :
Tổng các hằng số rút gọn là 81, vì vậy cận d- ới cho tất cả các hành trình là 81 (nghĩa là không thể tìm đ- ợc hành trình có tổng chi phí nhỏ hơn 81) .
Bây giờ, ta xét cách phân tập các ph- ơng án ra thành hai tập. Giả sử ta chọn cạnh (6,3) để phân nhánh. Khi đó tập các hành trình sẽ đ- ợc chia làm hai