www.agora.ro

Metode și tehnici

Branch&Bound și backtracking

Ovidiu Gheorghioiu

Poate autorul acestui articol nu e singurul programator care a avut aversiuni față de metoda Branch&Bound. Ne-a mărturisit că dacă îi "pica" la vreun concurs o problemă de acest tip, de regulă prefera să încerce o altă metodă de rezolvare. A căutat o soluție alternativă la B&B și a găsit o metodă cunoscută: backtracking, sub o formă modificată. Dacă e implementată corect, metoda dă rezultate mult mai bune decât B&B chiar la problemele clasice, iar programele sunt mult mai scurte.

Ideea

  O problemă tipică, la care de regulă se recomandă metoda Branch&Bound (de exemplu: Perspico), nu se poate rezolva cu metoda backtracking (în adâncime), deoarece nu se știe cât de departe trebuie coborât în arborele pozițiilor. Propunem ca acest arbore să se genereze tot ca la metoda Branch&Bound, nivel cu nivel, la fiecare pas regenerând nivelurile anterioare.

procedure Back(k:Integer);

begin

if {solutie}

then

begin

tipareste;

Halt

end;

if k>max then Break;

{ pentru fiecare mutare valida: }

{ – efectueaza mutarea; }

{ – Back(k+1); }

{ – restaureaza starea anterioara; }

end;

Begin

...

for max:=1 to max_posibil do

Back(1);

...

End.

Complexitatea

  Cred că deja se înțelege esența propunerii. Problema regenerării nivelurilor pare destul de complicată dar, un calcul simplu ne arată că nu afectează prea mult complexitatea, pentru că în general fiecare nivel este mult mai mare decât cele anterioare.

Dacă presupunem că, la fiecare pas, sunt în medie q posibilități, putem observa că cei doi algoritmi se comportă asemănător în ceea ce privește complexitatea. Ignorând deocamdată eventualele posibilități de optimizare, avem:

1) Nivelul de ordin n are dimensiunea qn.

2) Metoda Branch&Bound generează nivelul n în:

O(q1+q2+...+qn)=O((qn+1-1)/(q-1))=

=O(qn+1/(q-1)).

3) Metoda backtracking generează la pasul n nivelul n în aceeași complexitate.

Dar în cazul aplicării metodei backtracking am generat și nivelurile anterioare, rezultă că de fapt avem o complexitate de ordinul:

O((q2+q3+q4+...+qn+1)/(q-1))=O(qn+2/(q-1)), adică timpul de executare în cazul folosirii metodei backtracking este în medie de q ori mai mare decât în cazul folosirii metodei Branch&Bound. Dar, ținând cont de faptul că valoarea lui q este constantă, rezultă că (din punct de vedere practic) au aceeași complexitate!

Pe de altă parte, un pas în cazul metodei backtracking este mult mai simplu decât un pas în cazul metodei B&B, deoarece avem deja poziția în memorie și aceasta trebuie doar modificată, în schimb în cazul metodei B&B trebuie împachetată/despachetată, trebuie refăcute diferite calcule etc. Dacă ținem cont de marele dezavantaj al metodei Branch&Bound - și anume faptul că este mare consumatoare de memorie, rezultă că observația că soluția de mai sus funcționează mult mai bine decât Branch&Bound, nu trebuie să ne mire.

Totuși, metoda Branch&Bound nu trebuie "aruncată la coș", deoarece are avantajele sale cunoscute: de exemplu, dacă spațiul pozițiilor este limitat, aplicând metoda B&B vom obține soluții mai rapide. În plus, se pot tria soluțiile în memorie după o funcție de cost (în general euristică). Dar această modalitate funcționează numai în cazul în care funcția este "foarte bună" și nici chiar atunci nu este fiabilă pentru valori prea mari, deoarece în procesul trierii se pierd soluții posibile, datorită limitelor memoriei.

Evident, și metoda propusă mai sus poate fi optimizată, folosind "artificii de programare" cunoscute. Avantajul constă în faptul că la orice pas se știe cât de departe se poate avansa în spațiul soluțiilor. Dacă este posibilă estimarea corectă a numărului aproximativ de mutări se poate ieși dintr-o ramură care probabil nu conduce la soluție. Această optimizare a fost experimentată pe problema Perspico și rezultatele confirmă "prezicerile": un program neoptimizat găsește soluția în cazul în care aceasta este o secvență de cel mult 20-22 de mutări, iar unul optimizat găsește soluția chiar dacă aceasta este o secvență de până la 50 de mutări!

În continuare vă prezentăm sursele (problema Perspico rezolvată cu Branch&Bound apoi, rezolvată cu metoda propusă):

Listing BB.PAS

Vă prezentăm în continuare textul sursă al programului care implementează algoritmul propus, cu optimizarea posibilă, menționată.

listin_perspico.PAS

Vă propunem să vă "jucați" cu cele două alternative de rezolvare, experimentându-le, comparându-le pentru datele din următorul fișier de intrare:

PERSPICO.IN

6 2 8 1

13 5 7 4

14 15 3 11

9 0 10 12

2 0 7 1

6 8 13 4

14 11 12 10

3 5 9 15

2 6 8 4

5 3 1 15

14 13 11 12

7 10 0 9

8 2 5 11

1 6 9 10

3 15 0 7

12 14 4 13

5 3 13 15

6 14 10 8

7 12 11 2

4 9 1 0

1 2 3 4

5 6 0 7

9 14 15 8

13 12 10 11

8 2 5 11

1 6 9 10

3 15 0 7

12 14 4 13

Ovidiu Gheorghioiu este student la Universitatea Politehnică din București. Poate fi contactat prin e-mail la adresa: ovy@iname.com.

[cuprins]