Prin traversarea unui arbore se înţelege execuţia unei anumite operaţii asupra tuturor nodurilor
arborelui. În timpul vizitării nodurile sunt vizitate într-o anumită ordine, astfel încât ele pot fi considerate
ca şi cum ar fi integrate într-o listă liniară. Există trei moduri de ordonare (traversare) a unei structuri de
arbore, numite preordine, inordine şi postordine.
struct _nodArb{ int cheie; ...info; struct _listaPFii *lf; }; struct _listaPFii{ struct _nodArb *fiu; struct
_listaPFii *urm; };
Arbori binary
Prin arbore binar se înţelege o mulţime de n noduri care dacă nu este vidă, conţine un anumit nod numit
rădăcină, iar restul nodurilor formează doi arbori binari disjuncţi, numiţi subarborele stâng şi
subarborele drept.
// Implementarea dinamică a arborilor binari Fiecare nod trebuie să conţină, pe lângă cheie şi alte
informaţii specifice, adresele celor cel mult doi fii ai săi. Structura de date folosită în această
implementare este următoarea: struct _nodAB{ int cheie; ...info; _nodAB *stang; _nodAB *drept; };
Aşadar, având un arbore binar ordonat şi N un nod oarecare al său cu cheia C, toate nodurile din
subarborele stâng al lui N au cheile mai mici sau egale cu C şi toate nodurile din subarborele drept al lui
N au chei mai mari sau egale decît C. De aici rezultă un procedeu foarte simplu de căutare a unui nod cu
o cheie dată într-un arbore binar ordonat, şi anume: începând cu rădăcina, se trece la fiul stâng sau la
fiul drept după cum cheia căutată este mai mică sau mai mare decât cea a nodului curent. Numărul
comparaţiilor efectuate în acest caz este cel mult egal cu înălţimea arborelui.
Pentru adăugarea unui nou nod în arbore, acesta trebuie întâi creat, apoi inserat în arbore. Pe lângă
acestea, mai trebuie iniţializate câmpurile cu informaţii specifice nodului. Dacă arborele este vid, noul
nod va fi rădăcina lui. Dacă nu este vid, pentru inserarea noului nod, adresa acestuia trebuie memorată
în nodul care-i va fi părinte. Pentru a ajunge la acesta, trebuie întâi să-l căutăm.
Căutarea unui nod este mai simplă la arborii binari ordonaţi decât la cei neordonaţi, întrucât nu este
nevoie să parcurgem arborele până la găsire printr-una din metode (preordine, inordine, postordine).
Pornim de la rădăcină şi urmăm un drum fără reveniri în arbore, în funcţie de cheia căutată.
Căutarea se poate simplifica aplicând metoda fanionului şi modificând structurile arborelui astfel încât
orice referinţă către NULL se înlocuieşte cu o referinţă spre nodul fanion
Folosind fanion, înainte de începerea căutării, cheia nodului fanion se asignează cu valoarea căutată, x;
astfel, va exista în arbore cel puţin un nod cu acea cheie, şi, în cel mai rău caz, nodul va fi găsit pe
această poziţie.
Daca cautam sau vrem sa adaugam un nod in arbore trebuie sa o facem astfel incat arborele sa ramana
ordonat.
Pentru a suprima nodul cu cheia x dintr-un arbore binar ordonat, mai întâi se caută dacă există un astfel
de nod în arbore. Dacă nu, suprimarea s-a încheiat. În caz că nodul există, atunci se suprimă nodul,
procedându-se de o asemenea manieră încât arborele să rămână ordonat şi în urma suprimării. În
procesul de suprimare se disting două cazuri, după cum nodul de suprimat are cel mult un fiu sau are doi
fii, şi anume: - Dacă nodul de suprimat are cel mult un fiu: în această situaţie referinţa care indica spre
nodul de suprimat (un câmp al tatălui nodului de suprimat sau, în cazul rădăcinii, referinţa spre
rădăcină), se modifică astfel încât să indice unicul fiu al nodului de suprimat, dacă acesta există, sau în
caz contrar, valoarea lui devine NULL.
Dacă nodul de suprimat are doi fii: - se caută predecesorul nodului în ordonarea în inordine. Se poate
demonstra că acest nod există şi că el nu are fiu drept; - se modifică nodul de suprimat asignând toate
câmpurile sale de date cu câmpurile corespunzătoare ale predecesorului. În acest moment predecesorul
se găseşte în dublu exemplar în structura de arbore: în locul său iniţial şi în locul nodului de suprimat; -
se suprimă predecesorul conform cazului anterior, deoarece acesta nu are fiu drept. Predecesorul Y al
unui nod X se poate detecta prin următoarea metodă: se construieşte o secvenţă de noduri care începe
cu fiul stâng al lui X, după care se alege drept succesor al fiecărui nod, fiul său drept. Primul nod al
secvenţei care nu are fiu drept este predecesorul.
Arbori echilibraţi AVL
un arbore este echilibrat dacă şi numai dacă înălţimile celor doi subarbori ai săi diferă cu cel mult 1.
Arborii care satisfac acest criteriu numesc arbori AVL (Andelson-Velskii şi Landis). În cele ce urmează,
aceşti arbori vor fi numiţi şi arbori echilibraţi.
int cheie;
...info;
}nodG;
nod[nrN].cheie = x;
// + initializarea celorlalte informatii ale nodului
for(i=1;i<=nrN;i++)// initializarea cu 0 a conexiunilor cu celelalte noduri
arc[i][nrN] = arc[nrN][i] = 0;
int cheie;
...info;
}nodL;
Pentru a urmări vizual mai uşor conexiunile, în locul pointerilor putem reprezenta direct
cheia nodul indicat:
1- 2 3 4
2- 1 3 5
3- 1 2
4- 1 6 7
5- 2 6
6- 4 5 7
7- 4 6
8- 9
9- 8
Traversarea în adâncime utilizând implementarea cu matrici de adiacență
Dacă în graf au rămas noduri nevizitate, se selectează unul dintre acestea pe post de nod de
pornire, şi procesul anterior se repetă, până când toate nodurile grafului au fost vizitate.
În procesul de traversare, arcele care au fost traversate în decursul trecerii de la un nod
la altul formează aşa numitul arbore de acoperire prin traversare în adâncime al grafului
respectiv (dacă graful nu este conex, se obţin mai mulţi arbori, câte unul pentru fiecare
componentă conexă). Pentru un graf dat arborele de acoperire obţinut prin căutare în
adâncime nu este unic, acesta depinzând de modul de reprezentare al grafului precum şi de
punctul de pornire, care impune ordinea în care arcele conectate la anumit nod sunt vizitate.
Din acest motiv, arborii de acoperire diferă ca formă.
Componenta conexă 2: arborele de acoperire prin căutare în adâncime este format din arcul: (8, 9).
. Traversarea prin cuprindere utilizând implementarea cu matrici de adiacență
Principiul traversării (căutării) prin cuprindere este următorul: pentru fiecare nod vizitat
x, se caută în imediata sa apropiere cuprinzând, în vederea vizitării, toate nodurile adiacente
lui. Se foloseşte o structură de coadă pentru a reţine nodurile care au fost vizitate, iniţial
aceasta fiind vidă.
În mod analog cu traversarea prin căutare în adâncime, se poate construi şi în acest caz
arborele de acoperire pentru un graf dat, numit arborele de acoperire prin traversare prin
cuprindere. De asemenea, pot fi evidenţiate în aceeaşi manieră componentele conexe ale
grafului, traversarea făcându-se prin epuizarea nodurilor unei componente conexe şi apoi
trecerea la următoarea componentă.
Un arbore de acoperire minim al unui graf ponderat este arborele de acoperire al cărui
cost este cel puţin la fel de mic ca şi costul oricărui arbore de acoperire al grafului. Pentru un
graf pot exista mai mulţi arbori de acoperire minimi.
Gradul unui nod x , notat cu d(x),reprezinta numarul muchiilor incidente care trec prin
nodul x.
Un graf se numeşte conex dacă de la fiecare nod al său există un drum spre oricare nod al
grafului, respectiv dacă oricare pereche de noduri aparţinând nodului este conectată. Un graf
care nu este conex este format din componente conexe.
Fie G=(V, E) un graf orientat, unde V are n elemente (n varfuri) si E are m elemente (m arce).
Definitie: G1=(V1, E1)este o componenta tare conexa daca:
- pentru orice pereche x,y de varfuri din V1 exista un
drum de la x la y si drum de la y la x
- nu exista alt subgraf al lui G, G2=(V2, E2) care sa
indeplineasca prima conditie si care sa-l contina pe G1
Caracteristicile arborilor B
•
fiecare pagină conţine cel mult 2n noduri (chei)
•
fiecare pagină, cu excepţia uneia singure numită
pagină rădăcină, conţine cel puţin n noduri
•
fiecare pagină este fie o pagină terminală (caz în
care nu are descendenţi), fie are m+1
descendenţi, unde m reprezintă numărul de chei
în pagină
•
toate paginile terminale apar la acelaşi nivel
Principiul algoritmului este următorul: fie G graful ponderat pentru care se doreşte
determinarea arborelui de acoperire minim şi fie N mulţimea nodurilor acestuia. Algoritmul
începe prin selecţia unui nod de pornire şi introducerea lui în mulţimea U. În continuare, într-o
manieră ciclică, se selectează la fiecare pas arcul cu cost minim (u, v) care conectează un nod
din mulţimea U cu un alt nod din mulţimea V = N – U şi se adaugă acest arc arborelui de
acoperire minim, iar nodul v lui U. Ciclul se repetă până când toate nodurile au trecut în
submulţimea U, adică U = N.
Pasul 5: nod = 1 2 3 4 5 6 7
apropiat = 1 1 1 3 6 4 6
Pasul 6: nod = 1 2 3 4 5 6 7
apropiat = 1 1 1 3 6 4 6
Astfel, pentru graful din figura 9.1, aplicarea algoritmului lui Prim conduce la arborele de
acoperire minim format din arcele: (1, 3), (3, 4), (4, 6), (6, 7), (6, 5) şi (1,2), reprezentat în figura
9.2.
Tehnica căutării bazată pe prioritate folosită pentru determinarea arborelui de
acoperire minim este ca principiu identică cu algoritmul lui Prim, diferenţele constând doar în
modul de implementare. Principiul este: alegem nodul rădăcină, apoi, până la adăugarea în
arbore a tuturor nodurilor, la fiecare pas alegem nodul legat prin cea mai „ieftină” muchie de
unul dintre nodurile alese deja.
Nodurile nealese care sunt legate direct de cel puţin un nod dintre cele alese formează
aşa-numita „vecinătate”. Nodul ales la fiecare pas, aparţine, aşadar, vecinătăţii. El va fi cel cu
prioritatea maximă. În acest caz, „prioritatea maximă” o are nodul care conduce la arcul cu
ponderea minimă.
Metoda utilizează o coadă bazată pe prioritate în cadrul căreia sunt introduse, pe rând,
toate nodurile din clasa „vecinătate”, în funcţie de prioritatea acestora (cost mic, prioritate
mare). Rând pe rând, aceste noduri sunt extrase din coada cu prioritate (având în vedere că
nodul cu prioritate maximă, deci cost minim, va fi întotdeauna primul). La extragerea unui nod,
x, acesta va fi marcat (inclus în arborele de acoperire minim), apoi, pentru fiecare nod adiacent
lui x şi neales, y, se încearcă introducerea acestuia în coada cu prioritate cu prioritatea p egală
cu ponderea arcului (x, y). În coada cu prioritate, nodurile sunt întotdeauna ordonate în funcţie
de prioritatea lor. La încercarea de a introduce pe y cu prioritatea p în coadă, avem cazurile:
- cu o prioritate mai mare sau egală cu p (cost mai mic pentru ajunge la el de la
nodurile deja alese), nu-l mai introducem
1 1
2 8 2
1 1
2 4 3 2 4 2 3 2 4
6 2 2
2 8 7 3
7 7
1 1 5 1 1
5 6 6
Figura 11.1. Graf orientat şi ponderat Figura 11.2. Drumurile minime din 1
O problemă derivată din problema drumurilor minime cu origine unică, pe care
algoritmul lui Dijkstra prezentat mai sus o rezolvă, o reprezintă problema determinării
drumurilor minime corespunzătoare tuturor perechilor de noduri. Evident, această problemă
poate fi rezolvată şi cu ajutorul algoritmului lui Dijkstra, considerând pe rând, fiecare nod al
grafului drept origine. O altă posibilitate o reprezintă algoritmul lui Floyd.
Acesta utilizează o matrice a de dimensiuni nrNoduri x nrNoduri, unde memorează
lungimile drumurilor minime. Iniţial, a[i, j] = cost[i, j], pentru i != j; dacă nu există arc de la i la
j, cost[i, j] = mare. Elementele diagonalei principale în matricea a se iniţializează la zero.
Algoritmul execută nrNoduri iteraţii asupra matricii a, rezultând pe rând matricile a1, a2,
..., anrNoduri. După cea de-a k-a iteraţie, a[i, j] va conţine costul drumului minim de la i la j, care
nu trece prin nici un nod cu număr mai mare decât k, notat cu ak[i, j]
(figura 11.5). Astfel, pentru calcul lui ak[i, j], se compară ak-1[i, j], adică costul drumului de la i la
j fără a trece prin nodul k, şi nici printr-un alt nod cu număr mai mare decât k, cu ak-1[i, k] + ak-
1[k, j], adică costul drumului de la i la k însumat cu costul drumului de la k la j, fără a trece prin
nici un nod cu număr mai mare sau egal cu k. Dacă acesta din urmă se dovedeşte a fi mai scurt,
atunci costul acestuia se atribuie lui ak[i, j]; altfel, acesta rămâne neschimbat.
matricea drumurilor: matricea costurilor:
1 2 3 4 5 6 7 1 2 3 4 5 6 7
1 0 0 0 3 7 7 4 1 0 2 1 3 7 6 5
2 4 0 0 3 0 7 4 2 14 0 4 6 6 9 8
3 4 7 0 0 7 7 4 3 10 8 0 2 6 5 4
4 0 7 1 0 7 7 0 4 8 6 9 0 4 3 2
5 4 0 2 3 0 7 4 5 16 2 6 8 0 11 10
6 4 5 5 0 0 0 4 6 11 3 7 3 1 0 5
7 6 6 6 6 6 0 0 7 12 4 8 4 2 1 0