Arbori binari de cautare BST, dinamic echilibrat AVL, Structuri elementare si abstracte

Arbori binari de căutare (BST).
Tabelele Hashing - o alta solutie pentru a retine elementele din S. Complexitatea pentru arborele binar de cautare in cazurile: cel mai favorabil O(n), mediu O(logn). arbore binar de cautare - arbore T ale carui noduri sunt etichetate cu atomii continuti la un moment dat în dictionar. r ∈ V (radacina arborelui), Ts - subarborele stâng al radacinii si Td - subarborele drept al radacinii, atunci structura acestui arbore este definita de urmatoarele proprietati: 1)  ∀ nod x ∈ Ts atunci key(data(x)) < key(data(r));  2)  ∀ x ∈ Td atunci key(data(x)) > key(data(r)); 3)  Ts si Td sunt BST.

Funcții:
Search
search(rad,k)  // rad - pointer la radacina arborelui
{  // k - cheia de cautare a arborelui cautat
if (rad = 0)  then  return NULL
else
if key (data (rad)) > k then
return search (lchild (rad))
else
if key (data (rad)) < k then
return search (rchild (rad))
else    return rad }
Insert
Se va crea un nod în arbore care va fi plasat la un nou nod terminal. Pozitia în care trebuie plasat acesta este unic determinata în functie de valoarea cheii de cautare
vom insera 19 în arborele nostru:
insert(rad,a)  // rad  referinta la pointerul la radacina
// arborelui
{
if (rad= 0) then  rad= make_nod(a)
else  if key (data (rad)) > key(a) then
insert(lchild(rad),a)
else if key (data(rad)) < key(a)then
insert (rchild (rad),a)
}
 make_nod creaza un nou nod:

make_nod(a)
{
p= get_sp() // alocare de memorie pentru un nod nou
data(p)= a
lchild(p)= 0
rchild(p)= 0
return(p)
}
La inserarea unui atom deja existent în arbore, functia insert nu modifica structura arborelui. Exista probleme în care este utila contorizarea numarului de inserari a unui atom în arbore.
Functia insert poate returna pointer la radacina facând apeluri de forma p= insert(p,a).

Delete
delete(rad,k)  // rad - referinta la pointer la radacina
{    // k - cheia de cautare a atomului care trebuie sters de noi
if rad = 0 then
return  // nodul cu cheia k nu se afla în arbore
else if key(data(rad)) > k  then delete(lchild(rad),k)
else if key(data(rad)) < k then delete(rchild(rad),k)
else delete_root(rad)
}

Stergerea radacinii unui BST:
rad ⇒ arbore vid
2)  a)    rad             sau  b)    rad    ⇒   a)  SAS        sau         b)  SAD
/                                 \
SAS                              SAD
delete_root(rad)  // rad - referinta la pointer la radacina
{
if lchild(rad)=0 then
p= rchild(rad)
ret_sp(rad)
rad= p
else if rchild(rad)= 0 then
p= lchild(rad)
ret_sp(rad)
rad= p
else  p= remove_greatest(lchild(rad))
lchild(p)= lchild(rad)
rchild(p)= rchild(rad)
ret_sp(rad)
rad= p
}

Detașarea celui mai mic (mai mare nod) din arborele BST.
Pentru a gasi cel mai mare nod dintr-un arbore binar de cautare, se înainteaza în adâncime pe ramura dreapta pâna se gaseste primul nod care nu are descendent dreapta. Acesta va fi cel mai mare. Vom trata aceasta procedura recursiv:
Caz1:  rad ⇒ se returneaza pointer la radacina si arborele rezultat va fi vid.
Caz2:  rad ⇒ se returneaza pointer la radacina si arborele rezultat va fi format doar din subarborele stâng.
Caz3:  rad ⇒ functia returneaza pointer la cel mai mare nod din SAD, iar rezultatul va fi SAS - arborele care este fomat din radacina, SAS si SAD - cel mai mare nod.
remove_greatest(rad)  //rad -referinta la pointer la      
//radacina: un pointer la radacina de poate fi modificat de catre functie
{
if rchild (rad)= 0 then
p= rad
rad= lchild (rad)
return(p)
else return (remove_greatest (rchild(rad)))
}
Functia remove_greatest modifica arborele indicat de parametru, în sensul eliminarii nodului cel mai mare, si întoarce pointer la nodul eliminat.
Complexitatea tuturor functiilor scrise depinde de adâncimea arborelui. În cazul cel mai defavorabil, fiecare functie parcurge lantul cel mai lung din arbore. Functia de cautare are, în acest caz, complexitatea O(n). Structura arborelui BST este determinata de ordinea inserarii. De exemplu, ordinea 15 13 12 11 este alta decât 15 12 11 13 .

Crearea unui BST pornind de la secvența de atomi.
Studiem un caz de complexitate medie:
 gen_BST (va fi în programul principal)
rad= 0
for i= 1 to n
insert (rad, ai)
Calculam complexitatea medie a generarii BST: ∑ (i)=O(n*n), i=[1,n]
Notam cu T(k) - numarul de comparatii mediu pentru crearea unui BST pornind de la o secventa de k elemente la intrare. Ordinea celor k elemente se considera aleatoare. Pentru problema T(n)  avem de creat secventa  (a1  a2  ... an)  cu observatia ca a1  este radacina
arborelui. Ca rezultat, în urma primei operatii de inserare pe care o facem, rezulta:
a1 –a1 (ai
-ai (ai>a1)
Consideram numarul de operatii globale. Dupa ce am inserat a1, pentru inserarea fiecarui element în SAS sau SAD a lui a1, se face o comparatie cu a1.
T(n)=(n-1) + val.med.SAS + val.med.SAD
val.med.SAS = valoarea medie a numarului de comparatii necesar pentru a construe subarborele stâng SAS
val.med.SAD = valoarea medie a numarului de comparatii necesar pentru a construi subarborele drept SAD
T(n)=(n-1)+1/n*∑T(i-1)+1/n*∑T(n-1)
i=[1,n]; i=[i+1,n].
Ti(n) = numarul mediu de comparatii necesar pentru construirea unui BST cu n noduri atunci când prima valoare inserata (a1) este mai mare decât i-1 dintre cele n valori de inserat. Putem scrie:
Ti(n)=(n-1)+T(i-1)+T(n-i)
T(n)+1/n*∑Ti(n)
Deci
T(n)=2/n*∑T(i); i=[0,n-1]
Complexitatea acestei functii este: O(n⋅ln n)

Arbori binari de căutare dinamică echilibrata (AVL).
Un arbore binar este echilibrat daca si numai daca, pentru fiecare nod din arbore, diferenta dintre adâncimile SAS si SAD în modul este ≤ 1. Adâncimea unui arbore echilibrat cu n noduri este O(ln n). Se completeaza operatiile insert si delete cu niste prelucrari care sa pastreze proprietatile de arbore binar echilibrat pentru arborele binar de cautare. Arborele binar echilibrat este un BST echilibrat, proprietatea de echilibrare fiind conservata de insert  si  delete. Efortul, în plus, pentru completarea operatiilor  insert  si delete nu schimba complexitatea arborelui binar echilibrat. Transformarea structurii arborelui dupa inserare pentru a conserva proprietatea de arbore binar
Echilibrat. Modificarile care se vor face se vor numi rotatii. în inserarea prin rotatie se obtine un arbore echilibrat cu adâncimea egala cu adâncimea arborelui de dinainte de inserare. La inserarea unui nod terminal într-un arbore AVL este necesara aplicarea a cel mult o rotatie asupra unui nod. Trebuie, deci sa gasim nodul asupra caruia trebuie aplicata rotatia. S-a notat pentru fiecare nod bf - balance factor (factor de dezechilibrare):
bf(nod) = depth (lchild (nod)) - depth (rchild (nod))
Calculam factorii de balansare dupa inserare.
Pentru nodul terminal s-a schimbat adâncimea si factorul de balansare; bf = -2 dupa inserare
devine nod dezechilibrat. Trebuie aplicata, deci, echilibrarea. Se numeste nod critic primul nod cu bf ≠ 0 întâlnit la o parcurgere de jos în sus a ramurii care leaga nodul
inserat de radacina. Toate nodurile din ramura care sunt pe nivele inferioare nodului critic vor capata bf = 1 sau  bf = -1. La nodul critic exista doua situatii: 1. Nodul critic va fi perfect balansat (bf = 0), daca dezechilibrul creat de nodul inserat anuleaza dezechilibrul initial al nodului. În acest caz nu este nevoie de rotatie (el completeaza un gol în arbore). 2. Factorul de balansare devine bf = 2 sau bf = -2 atunci când nodul inserat mareste dezechilibrul arborelui
(s-a inserat nodul în subarborele cel mai mare). În acest caz, se aplica o rotatie în urma careia se schimba strucutra subarborelui, astfel încât noua radacina capata bf = 0, conservându-se adâncimea. Problema conservarii proprietatii de echilibrare a arborelui se rezolva aplicând o rotatie asupra nodului critic numai atunci când inserarea dezechilibreaza acest nod. Costul suplimentar care trebuie suportat se materializeaza prin necesitatea ca în fiecare nod sa se
memoreze factorul de dezechilibrare bf. Acesti factori de dezechilibrare vor fi actualizati în urma operatiilor de rotatie si inserare. Operatia de stergere într-un AVL implica mai multe rotatii.

Structuri de date elementare şi abstracte.
Un vector este o colectie de date de acelasi tip, în care elementele colectiei sunt identificate prin indici ce reprezintă pozitia relativă a fiecărui element în vector. La început se puteau declara si utiliza numai vectori cu dimensiuni fixe, stabilite la scrierea programului si care nu mai puteau fi modificate la executie. Introducerea tipurilor pointer si alocării dinamice de memorie în limbajele Pascal si C a permis utilizarea de vectori cu dimensiuni stabilite si/sau modificate în cursul executiei programelor. Gruparea mai multor date, de tipuri diferite, într-o singură entitate, numită “structură” în C a permis definirea unor noi tipuri de date de către programatori si utilizarea unor date dispersate în memorie, dar legate prin pointeri: liste înlăntuite, arbori si altele. Astfel de colectii se pot extinde dinamic pe măsura necesitătilor si permit un timp mai scurt pentru anumite operatii, cum ar fi operatia de căutare într-o colectie. Limbajul C asigură structurile de date fundamentale (vectori, pointeri, structuri) si posibilitatea combinării acestora în noi tipuri de date, care pot primi si nume sugestive prin declaratia typedef. Dintr-o perspectivă independentă de limbajele de programare se pot considera ca structuri de date fundamentale vectorii, listele înlăntuite si arborii, fiecare cu diferite variante de implementare. Alte structuri de date se pot reprezenta prin combinatii de vectori, liste înlăntuite si arbori. De exemplu, un tabel de dispersie (“Hash table”) este realizat de obicei ca un vector de pointeri la liste înlăntuite (liste de elemente sinonime). Un graf se reprezintă deseori printr-un vector de pointeri la liste înlăntuite (liste de adiacente), sau printr-o matrice (un vector de vectori în C).
Clasificarea structurilor de date:
Un criteriu de clasificare foloseste relatiile dintre elementele colectiei:
Colectii  liniare  (secvente,  liste),  în  care  fiecare  element  are  un  singur  succesor  si  un  singur predecesor;
Colectii arborescente (ierarhice), în care un element poate avea mai multi succesori (fii), dar un singur predecesor (părinte);
Colectii neliniare generale, în care relatiile dintre elemente au forma unui graf general (un element
poate avea mai multi succesori si mai multi predecesori).
Un alt criteriu poate fi modul de reprezentare a relatiilor dintre elementele colectiei:
Implicit, prin dispunerea lor în memorie (vectori de valori, vectori de biti, heap);
Explicit, prin adrese de legătură (pointeri).
Un alt criteriu grupează diferitele colectii după rolul pe care îl au în aplicatii si după operatiile
asociate colectiei, indiferent de reprezentarea în memorie, folosind notiunea de tip abstract de date. Astfel putem deosebi:
Structuri de căutare (multimi si dictionare abstracte);
Structuri de păstrare temporară a datelor (containere, liste, stive, cozi s.a.)
Un tip abstract de date este definit numai prin operatiile asociate (prin modul de utilizare), fără referire la modul concret de implementare.
Pentru programele nebanale este utilă o abordare în (cel putin) două etape:
o etapă de conceptie (de proiectare), care include alegerea tipurilor abstracte de date si algoritmilor necesari;
o etapă de implementare (de codificare), care include alegerea structurilor concrete de date, scrierea de cod si folosirea unor functii de biblioteca.
Problema alegerii unei structuri de date eficiente pentru un tip abstract nu are o solutie unică, desi există anumite recomandări generale în acest sens. Sunt mai multi factori care pot influenta această alegere si care depind de aplicatia concretă.
Exemplul cel mai bun este al structurilor de date folosite atunci când sunt necesare căutări frecvente într-o colectie de date după continut (după chei de căutare); căutarea într-un vector sau într-o listă înlăntuită este ineficientă pentru un volum mare de date si astfel au apărut tabele de dispersie (“hash table”), arbori de căutare echilibrati, arbori B si alte structuri de date optimizate pentru operatii de căutare.
Alt exemplu este cel al algoritmilor folositi pentru determinarea unui arbore de acoperire de cost
minim al unui graf cu costuri (Prim, Kruskal), care au o complexitate ce depinde de structurile de date folosite.
Eficienta unei anumite structuri este determinată de doi factori: memoria ocupată si timpul necesar pentru operatiile frecvente. Mai des se foloseste termenul de “complexitate”, cu   variantele “complexitate temporală” si “complexitate spatială”.

Popular Posts

Expresii frazeologice

Corespondenta economica

Exam la filozofie: Primele 24 intrebari

Analiza economico - financiara

Motive

Integrale

Finantele Intreprinderii exam

Dreptul Afacerilor T1

Genuri si specii

Integrarea Economica