Metode și tehnici Branch&Bound și backtrackingOvidiu 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.
IdeeaO 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. ComplexitateaCred 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ă):
Vă prezentăm în continuare textul sursă al programului care implementează algoritmul propus, cu optimizarea posibilă, menționată.
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] |