Sunteți pe pagina 1din 11

Capitolul 2

METODE DE C UTARE I PROGRAMARE

2.1. C utarea în l ime


În teoria grafurilor, breadth-first search (BFS) este un algoritm de
c utare în grafuri, care începe cu vârful r d cin i exploreaz toate nodurile
vecine. Apoi, pentru fiecare dintre aceste noduri se exploreaz nodurile
vecine înc necercetate, .a.m.d.., pân când scopul a fost atins.
BFS este o metod de c utare, care inte te extinderea i examinarea
tuturor nodurilor unui graf, cu scopul de a g si solu ia.
Din punct de vedere al algoritmului, toate nodurile „fii” ob inute prin
expansiunea unui nod sunt ad ugate într-o „coad ” de tipul FIFO (First In
First Out). În implement rile tipice, nodurile care nu au fost înc examinate
de c tre vecinii corespunz tori sunt plasate într-un „recipient” (asem n tor
unei cozi sau unei liste de leg tur ), numit „deschis”, iar odat examina i
sunt plasa i în „recipientul” „închis”.

2.1.1. Algoritmul
1. Introducerea nodului r d cin în coad .
2. Extragerea unui nod din cap tul listei i examinarea acestuia.
Dac elementul c utat se identific cu acest nod, se renun la
c utare i se returneaz rezultatul.
Altfel, se plaseaz to i succesorii (nodurile „fii”) (neexamina i
înc ) acestui nod la sfâr itul „cozii” (acesta în cazul în care
exist )
3. Dac „coada” este goal , fiecare nod al grafului a fost examinat -
se renun la c utare i se întoarce la „not found”.
4. Repet începând cu Pasul 2.

2.1.2. Implementarea C++


În continuare este implementarea algoritmului de mai sus, unde
„neexamina ii pân în momentul de fa ” sunt gestiona i de c tre tabloul
p rinte.
Fie structura struct i structura de noduri

struct Vertex {
...
std::vector<int> out;
...
};
std::vector<Vertex> graph(vertices);
bool BFS(const std::vector<Vertex>& graph, int start, int end) {
std::queue<int> next;
std::vector<int> parent(graph.size(), 0);
parent[start] = -1;
next.push(start);
while (!next.empty()) {
int u = next.front();
next.pop();
if (u == end) return true;
for (std::vector<int>::const_iteratorj=graph[u].out.begin();
j != graph[u].out.end(); ++j)
{
// Look through neighbors.
int v = *j;
if (parent[v] == 0) {
// If v is unvisited.
parent[v] = u;
next.push(v);
}
}
}
return false;
}

Sunt stoca i p rin ii fiec rui nod, de unde se poate deduce drumul.

2.1.3. Complexitate i optimalitate


Complexitate în spa iu. Având în vedere faptul c toate nodurile
descoperite pân la momentul de fa trebuiesc salvate, complexitatea în
spa iu a breadth-first search este O V E , unde V reprezint num rul
nodurilor, iar E num rul muchiilor grafului. Alt modalitate de a consemna
acest lucru: complexitate O B M , unde B reprezint cea mai lunga ramur ,
iar M lungimea maxim a drumului arborelui. Aceast cerere imens de
spa iu este motivul pentru care breadth-first search nu este practic în cazul
problemelor mai ample.
Complexitatea în timp. Odat ce, în cel mai r u caz breadth-first
search trebuie s ia în considerare toate drumurile c tre toate nodurile,
complexitatea în timp a acestui tip de c utare este de O(| V | | E |) . Cel mai
bun caz în aceast c utare este conferit de complexitatea O(1) . Are loc
atunci când nodul este g sit la prima parcurgere.
Completitudine. Metoda breadth-first search este complet . Aceast
înseamn c dac exist o solu ie , metoda breadth-first search o va g si,
indiferent de tipul grafului. Cu toate acestea, dac graful este infinit i nu
exist nici o solu ie, breadth-first search va “e ua”.
Optimalitate. Pentru costul unitar pe muchii, bread-first search este
o metod optim . În general, breadth-first search nu este o metod optim , i
aceasta deoarece returneaz întotdeauna rezultatul cu cele mai pu ine muchii
între nodul de start i nodul vizat. Dac graful este un graf ponderat, i drept
urmare are costuri asociate fiec rei etape, aceast problem se rezolv
îmbun t ind metoda breadth-first search astfel încât s se uniformizeze
costurile de c utare, identificate cu: costurile drumului. Totu i, dac graful
nu este ponderat, i prin urmare toate costurile etapelor sunt egale,
breadth-first search va identifica cea mai apropiat i optim solu ie.

2.1.4 Aplica ii ale BFS


Breadth-first search poate fi folosit pentru rezolvarea unei game
variate de probleme de teoria grafurilor, printre care:
• G sirea tuturor componentelor conexe dintr-un graf.
• Identificarea tuturor nodurilor într-o component conex .
• G sirea celui mai scurt drum între nodurile u i v (într-un graf
neponderat).
• Testarea biparti iei unui graf.
G sirea Componentelor Conexe
Mul imea vârfurilor accesate prin metode BFS reprezint cea mai
mare component conex care con ine vârful de start.
Testarea biparti iei
BFS poate fi folosit pentru testarea biparti iei, începând c utarea cu
orice vârf i atribuind etichete alternative vârfurilor vizitate în timpul
c ut rii. Astfel, se atribuie eticheta 0 vârfului de start, 1 tuturor vecinilor s i,
0 vecinilor acelor vecini, i a a mai departe. Dac într-un anumit moment al
procesului un vârf are vecini vizita i cu aceea i etichet , atunci graful nu este
bipartit. Dac parcurgerea se sfâr e te f r a se produce o astfel de situa ie,
atunci graful este bipartit.

2.2. C utarea în adâncime


Depth-first search (DFS) este un algoritm c utare a arborelui,
structurii arborelui, sau a grafului. Formal, DFS reprezint o c utare care
evolueaz prin expansiunea la primul vârf „fiu” a arborelui ce ia na tere pe
m sur ce se coboar în adâncime, pân în momentul în care vârful „ int ”
este descoperit sau pân când se întâlne te un vârf care nu are „fii”. La pasul
urm tor, c utarea se reia (backtracking), revenind la nodul cel mai recent
vizitat, îns pentru care explorarea nu este încheiat . Într-o implementare ne-
recursiv , toate vârfurile recent vizitate sunt ad ugate într-o stiv de tipul
LIFO (Last In First Out), în scopul explor rii acestora. Complexitatea în
spa iu a DFS este cu mult mai mic decât cea a BFS (Breadth-First Search).
De asemenea se preteaz mult mai bine metodelor euristice de alegere a
ramurilor asem n toare. Complexitatea în timp a ambilor algoritmi este
propor ional cu num rul vârfurilor plus num rul muchiilor grafului
corespunz tor O V E .
C utarea în adâncime se poate folosi i la ordonarea liniar a
vârfurilor grafului (sau arborelui). Exist trei astfel de posibilit i:
O preordine reprezint o listare a vârfurilor în ordinea în care au
fost vizita i prin intermediul algoritmului c ut rii în adâncime. Aceasta este
o modalitate natural i compact de descriere a progresului c ut rii. O
preordine a unei expresii arbore este ceea ce numim expresie în nota ia
Polonez .
O postordine reprezint o listare în care cel din urm vârf vizitat
este primul element al listei. O postordine a unui expresii arbore este de
fapt expresia în oglind a expresiei în nota ie Polonez .
O postordine inversat (în oglind ) este, de fapt, reversul
postordinii, i.e. o listare a vârfurilor în ordinea invers a celei mai recente
vizite a vârfurilor in cauz . În c utarea unui arbore, postordinea inversat
coincide cu preordinea, îns , în general, difer atunci când se caut un graf.
Spre exemplu când se caut graful:

începând cu vârful A, preordinile posibile sunt A B D C, respectiv A C D B


(în func ie de alegerea algoritmului de a vizita mai întâi vârful B sau vârful
C), în timp ce postordinile inversate (în oglind ) sunt: A B C D i A C B D.
Postordinea inversat produce o sortare topologic a oric rui graf orientat
aciclic. Aceast ordonare este folositore i în analiza fluxului de control,
reprezentând adesea o liniarizare natural a fluxului de control. Graful mai
sus amintit poate reprezenta fluxul de control într-un fragment de cod ca cel
de mai jos:
if (A) then {
B
} else {
C
}
D

i este natural s consider m c acest cod urmeaz ordinea A B C D sau A C


B D, îns nu este normal s urmeze ordinea A B D C sau A C D B.
PSEUDOCOD (recursiv)
dfs(v)
process(v)
mark v as visited
for all vertices i adjacent to v not visited
dfs(i)

O alt variant
dfs(graph G)
{
list L = empty
tree T = empty
choose a starting vertex x
search(x)
while(L is not empty)
{
remove edge (v, w) from beginning of L
if w not yet visited
{
add (v, w) to T
search(w)
}
}
}
search(vertex v)
{
visit v
for each edge (v, w)
add edge (v, w) to the beginning of L
}

Aplica ii
Iat câ iva algoritmi în care se folose te DFS:
G sirea componentelor conexe.
Sortarea topologic .
G sirea componentelor tare conexe.

2.3. Metoda Greedy


Descrierea metodei Greedy
Metoda Greedy (greedy = lacom) este aplicabil problemelor de
optim.
Consider m mul imea finit
A {a1 ,..., a n }
i o proprietate p definit pe mul imea submul imilor lui A:
p(0) 1
p : P(A) {0,1} cu
p(X) 1 p(Y) 1, Y X
O submul ime S A se nume te solu ie dac p(S) = 1.
Dintre solu ii va fi aleas una care optimizeaz o func ie de cost
p : P(A) R
dat .
Metoda urm re te evitarea c ut rii tuturor submul imilor (ceea ce ar
necesita un timp de calcul exponen ial), mergându-se "direct" spre solu ia
optim . Nu este îns garantat ob inerea unei solu ii optime; de aceea
aplicarea metodei Greedy trebuie înso it neap rat de o demonstra ie.
Distingem doua variante generale de aplicare a metodei Greedy:
Prima variant alege în mod repetat câte un element oarecare al
mul imii A i îl adaug solu iei curente S numai dac în acest mod se ob ine
tot o solu ie. În a doua variant procedura prel realizeaz o permutare a
elementelor lui A, dup care elementele lui A sunt analizate în ordine i
ad ugate solu iei curente S numai dac în acest mod se ob ine tot o solu ie.
Exemplu. Se consider mul imea de valori reale A {a1 ,..., a n } .
Se caut submul imea a c rei sum a elementelor este maxim .
Vom parcurge mul imea i vom selecta numai elementele pozitive,
care vor fi plasate în vectorul solu ie s.

k 0
for i = 1,n
if ai > 0
then k k + 1; sk ai
write(s)

2.4. Metoda backtracking


Un algoritm este considerat "acceptabil" numai dac timpul s u de
executare este polinomial, adic de ordinul O(nk) pentru un anumit k; n
reprezint num rul datelor de intrare.
Pentru a ne convinge de acest lucru, vom considera un calculator
capabil s efectueze un milion de opera ii pe secund . în tabelul urm tor apar
timpii necesari pentru a efectua n3, 2n i 3n opera ii, pentru diferite valori
mici ale lui n:

n = 20 n = 40 n = 60
n3 - - 0,2 sec
2n 1 sec 12,7 zile 366 secole
3n 58 min 3855 secole 1013 secole

Tabelul de mai sus arat c algoritmii exponen iali nu sunt


acceptabili.
Descrierea metodei Backtracking
Fie produsul cartezian X X1 ... X n . C utam x X cu (x) 1 ,
unde : X {0,1} este o proprietate definit pe X.
Din cele de mai sus rezult c generarea tuturor elementelor
produsului cartezian X nu este acceptabil .
Metoda backtracking încearc mic orarea timpului de calcul. X este
numit spa iul solu iilor posibile, iar sintetizeaz condi iile interne.
Vectorul X este construit progresiv, începând cu prima component .
Nu se trece la atribuirea unei valori lui x, decât dac am stabilit valori pentru
x1 ,..., x k 1 i k 1 (x1 ,..., x k 1 ) 1 . Func iile k : X1 ... X n {0,1} se
numesc condi ii de continuare i sunt de obicei restric iile lui la primele k
variabile. Condi iile de continuare sunt strict necesare, ideal fiind s fie i
suficiente.
Distingem urm toarele cazuri posibile la alegerea lui xk:
1) "Atribuie i avanseaz ": mai sunt valori neanalizate din Xk i
valoarea xk aleas satisface k=> se m re te k.
2) "Încercare e uat ": mai sunt valori neconsumate din Xk i
valoarea xk aleas dintre acestea nu satisface k=> se va relua,
încercându-se alegerea unei noi valori pentru xk.
3) "Revenire": nu mai exist valori neconsumate din Xk (Xk
epuizat ) întreaga Xk devine disponibil i k <- k-1.
4) "Revenire dup determinarea unei solu ii": este re inut solu ia.
Re inerea unei solu ii const în apelarea unei proceduri retsol care
prelucreaz solu ia i fie opre te procesul (dac se dore te o singur solu ie),
fie prevede k k-1 (dac dorim s determin m toate solu iile).
Not m prin Ck X k mul imea valorilor consumate din Xk.
Algoritmul este urm torul:

Ci<- 0, i;
k<-l;
while k > 0
if k = n+1
then retsol (x); k<- k-1;
else if C X
k k
then alege v X \ C ; C C k {v} ;
k k k
if
k (x1 ,..., x k 1 , v) 1
then x v ; k<- k+l;
k
else
else Ck<- 0 ; k<- k-l;
Pentru cazul particular X1 ... Xn {1,...,s} , algoritmul se
simplific
k<-l; x i <-0, i = 1, …, n;
while k > 0
if k = n+1
then retsol (x); k<-k-l;
else if x
k s
then x xk 1;
k
k (x1 ,..., x k ) 1
if
then k<- k+l;
else
else x <-0; k<-k-l;
k

2.5. Metoda divide et impera


Metoda Divide et Impera ("desparte i st pâne te") consta în
împ r irea repetat a unei probleme de dimensiuni mari în mai multe
subprobleme de acela i tip, urmat de rezolvarea acestora i combinarea
rezultatelor ob inute pentru a determina rezultatul corespunz tor problemei
ini iale. Pentru fiecare subproblem proced m în acela i mod, cu excep ia
cazului în care dimensiunea ei este suficient de mic pentru a fi rezolvat
direct. Este evident caracterul recursiv al acestei metode.
Schema general
Descriem schema general pentru cazul în care aplic m metoda
pentru o prelucrare oarecare asupra elementelor unui vector. Func ia DivImp,
care întoarce rezultatul prelucr rii asupra unei subsecven e a p ,..., a u , va fi
apelat prin DivImp .
function DivImp(p,u)
if u-p <
then r <- Prel (p, u)
else m <- Interm (p,u);
r1 <- DivImp (p,m);
r2 <- DivImp (m+1,u);
r <- Combin (r1,r2)
return r
end;

unde:
p u
- func ia Interm întoarce un indice în intervalul p..u; de obicei m = ;
2
- func ia Prel întoarce rezultatul subsecven ei p .. u, dac aceasta este
suficient de mic ;
- func ia Combin întoarce rezultatul asambl rii rezultatelor par iale r1 i r2.
2.6. Metoda Branch and Bound
Prezentare general
Metoda Branch and Bound se aplic problemelor care pot fi
reprezentate pe un arbore: se începe prin a lua una dintre mai multe decizii
posibile, dup care suntem pu i în situa ia de a alege din nou dintre mai
multe decizii; vom alege una dintre ele etc. Vârfurile arborelui corespund
st rilor posibile în dezvoltarea solu iei.
Deosebim dou tipuri de probleme:
1) Se caut un anumit vârf, numit vârf rezultat, care nu are descenden i.
2) Exist mai multe vârfuri finale, care reprezint solu ii posibile, dintre care
c ut m de exemplu pe cel care minimizeaz o anumit func ie.
De i metoda este aplicabil pe arbori. Exist multe deosebiri, dintre
care men ion m:
- ordinea de parcurgere a arborelui;
- modul în care sunt elimina i subarborii care nu pot conduce la o
solu ie;
- faptul ca arborele poate fi infinit (prin natura sa sau prin faptul c
mai multe vârfuri pot corespunde la o aceea i stare).
În general arborele de st ri este construit dinamic.
Este folosit o list L de vârfuri active, adic de st ri care sunt
susceptibile de a fi dezvoltate pentru a ajunge la solu ie / solu ii. Ini ial, lista
L con ine r d cina arborelui, care este vârful curent. La fiecare pas, din L
alegem un vârf (care nu este neap rat un fiu al vârfului curent!), care devine
noul vârf curent.
Când un vârf activ devine vârf curent, sunt genera i to i fiii s i, care
devin vârfuri active (sunt inclu i în L). Apoi din nou este selectat un vârf
curent.
Legat de modul prin care alegem un vârf activ drept vârf curent, deci
implicit legat de modul de parcurgere a arborelui, facem urm toarele
remarci:
- c utarea în adâncime nu este adecvat , deoarece pe de o parte
arborele poate fi infinit, iar pe de alt parte solu ia c utat poate fi
de exemplu un fiu al r d cinii diferit de primul fiu i c utarea în
adâncime ar fi ineficient : se parcurg inutil st ri, în loc de a avansa
direct spre solu ie;
- c utarea pe l ime conduce totdeauna la solu ie (dac aceasta
exist ), dar poate fi ineficient dac vârfurile au mul i fii.
Metoda Branch and Bound încearc un "compromis" între cele dou
c ut ri men ionate mai sus, ata ând vârfurilor active cate un cost pozitiv, ce
inten ioneaz sa fie o m sur a gradului de "apropiere" a vârfului de o
solu ie. Alegerea acestui cost este decisiv pentru a ob ine un timp de
executare cât mai bun i depinde de problema concret , dar i de abilitatea
programatorului.
Costul unui vârf va fi totdeauna mai mic decât cel al descenden ilor
(fiilor) s i.
De fiecare dat drept vârf curent este ales cel de cost minim (cel
considerat ca fiind cel mai "aproape" de solu ie).
Din analiza teoretic a problemei deducem o valoare lim care este o
aproxima ie prin adaos a minimului c utat: atunci când costul unui vârf
depaseste lim, vârful curent este ignorat: nu este luat în considerare i deci
este eliminat întregul subarbore pentru care este r d cin . Dac nu
cunoa tem o astfel de valoare lim, o ini ializ m cu + .
Se poate defini o func ie de cost ideal , pentru care c(x) este dat de:
nivelul pe care se afl vârful x dac x este vârf rezultat;
+ dac x este vârf final, diferit de vârf rezultat;
min {c(y) | y fiu al lui x} dac x nu este vârf final.
Aceast func ie este ideal din dou puncte de vedere:
- nu poate fi calculat dac arborele este infinit; în plus,
chiar dac arborele este finit, el trebuie parcurs în întregime, ceea
ce este exact ce dorim s evit m;
- dac totu i am cunoa te aceast func ie, solu ia poate
fi determinat imediat: plec m din r d cin i coborâm mereu spre
un vârf cu acela i cost, pân ajungem în vârful rezultat.
Neputând lucra cu func ia ideal de mai sus, vom alege o
aproxima ie d a lui c, care trebuie s satisfac condi iile:
1) în continuare, dac y este fiu al lui x avem
d(x) < d(y);
2) d(x) s poat fi calculat doar pe baza informa ilor din
drumul de la r d cin la x;
3) este indicat ca d c pentru a ne asigura c dac
d(x ) > lim, atunci i c(x) > lim, deci x nu va mai fi
dezvoltat.
O prim modalitate de a asigura compromisul între c ut rile în
adâncime i pe l ime este de a alege func ia d astfel încât, pentru o valoare
natural k, s fie îndeplinit condi ia: pentru orice vârf x situat pe un nivel
nx i orice vârf situat pe un nivel ny nx + k, s avem d(x) > d(y),
indiferent dac y este sau nu descendent al lui x.
Condi ia de mai sus spune c niciodat nu poate deveni activ un vârf
aflat pe un nivel ny nx + k dac în L apare un vârf situat pe nivelul nx,
adic nu putem merge "prea mult" în adâncime. Dac aceasta condi ie este
îndeplinit , este valabil urm toarea propozi ie:
Propozi ie.
În ipoteza c este îndeplinit condi ia de mai sus i dac exist
solu ie, ea va fi atins într-un timp finit, chiar dac arborele este infinit.
Algoritmul Branch & Bound pentru probleme de optim
S presupunem c dorim s determin m vârful final de cost minim i
drumul de la r d cin la el. Fie lim aproximarea prin adaos considerat mai
sus.
Algoritmul este urm torul (rad este r d cina arborelui, iar ifinal este
vârful rezultat):

i<-rad; L<={i}; min<-lim;


calcul m d(rad); tata(i)<-0
while L Ø
i <= L {este scos vârful i cu d(i) minim din min-ansamblul L}
for to i j fii ai lui i
calcul m d(j); calcule locale asupra lui j; tata(j)<-i
if j este vârf final
then if d(j)< min
then min<-d(j); ifinal<-j
elimin din L vârfurile k cu d(k) min (*)
else if d(j)<min
then j => L
if min =lim then write {'Nu exist solu ie')
else writeln (min); i<-ifinal
while i 0
write{i); i<-tata{i)

La (*) am inut cont de faptul c dac j este descendent al lui i, atunci


d(i ) < d(j).