Explorați Cărți electronice
Categorii
Explorați Cărți audio
Categorii
Explorați Reviste
Categorii
Explorați Documente
Categorii
Arborii sunt structuri de date dinamice i omogene. Cele mai comune utilizri ale arborilor sunt cutarea n volume mari de date i reprezentarea de structuri organizate ierarhic.
1. Arbori oarecare
Arborii oarecare sunt colecii de noduri care conin informaia util i legturi ctre descendeni. Fiecare arbore conine un nod iniial numit rdcin. Structura unui arbore oarecare este prezentat n figura urmtoare:
Pentru implementarea unui arbore oarecare n C/C++ se poate folosi o structur de forma:
// tipul de date al informatiilor utile typedef double TipArbore; // structura care reprezinta un nod din arbore struct NodArbore { // informatiile utile stocate in nod TipArbore Informatii; // numarul de legaturi catre fii int numarLegaturi; // vector de legaturi catre fii NodArbore **Legaturi; };
Fiecare nod conine informaiile utile, un ntreg care reine numrul de fii i un vector de pointeri ctre fii. Principalele operaii care pot fi implementate pe un arbore oarecare sunt: Operaie Adugare nod Descriere Adaug un nod n arbore dup un anumit criteriu (de exemplu la un anumit nod printe). Operaia presupune alocarea memoriei pentru nod, copierea informaiei utile i modificarea legturii printelui.
Presupune dealocarea memoriei pentru nodul respectiv i pentru toi descendenii si i modificarea legturii printelui. Presupune obinerea unei liste care conine toate informaiile utile din arbore. Presupune obinerea unui pointer la un nod pe baza unui criteriu de regsire.
2. Arbori binari
Arborii binari sunt arbori n care nodurile conin cel mult doi descendeni. Pentru memorarea acestor arbori se poate folosi o structur mai simpl de forma:
// tipul de date al informatiilor utile typedef double TipArbore; // structura care reprezinta un nod // dintr-un arbore binar struct NodArbore { // informatiile utile stocate in nod TipArbore Informatii; // vector de legaturi catre fii NodArbore *Stanga, *Dreapta; };
Operaiile sunt aceleai ca i n cazul arborilor oarecare. Exemplu: parcurgerea arborelui n ordinea stnga rdcin - dreapta i stocarea elementelor ntr-o list:
// nodul listei simplu inlantuite struct NodLista { // informatia utila TipArbore Informatii; // legastura catre elementul urmator NodLista *Legatura; // constructorul pentru initializarea unui nod NodLista(TipArbore info, NodLista* leg = NULL) : Informatii(info), Legatura(leg) {} }; // functie de concatenare a doua liste simplu inlantuite NodLista* Concatenare(NodLista *cap1, NodLista *cap2) { // cazul 1: prima lista e vida if (cap1 == NULL) return cap2; // cazul 2: prima lista e nevida // parcurgem prima lista while (cap1->Legatura != NULL) cap1 = cap1->Legatura; // si facem legatura cap1->Legatura = cap2; // intoarcem capul listei return cap1; } // procedura recursiva de parcurgere a unui arbore binar NodLista* Parcurgere(NodArbore *nod) { // conditia de oprire din recursie if (nod == NULL) return NULL; // initializam lista NodLista *cap = NULL; // adaugam subarborele stang cap = Concatenare(cap, Parcurgere(nod->Stanga)); // adaugam nodul curent cap = Concatenare(cap, new NodLista(nod->Informatii)); // adaugam subarborele drept cap = Concatenare(cap, Parcurgere(nod->Dreapta)); return cap; }
Arborii oarecare pot fi memorai ca arbori binari schimbnd semantica legturilor nodului astfel: legtura stnga va adresa primul fiu legtura dreapta va adresa urmtorul frate
Exemplu de transformare:
a) arbore oarecare:
Adugare Se caut folosind algoritmul de cutare poziia n arbore i se aloc memoria i se face legtura cu nodul printe. tergere Se caut nodul de ters i se terge nodul. Subarborele drept este avansat n locul nodului ters, iar subarborele stng este mutat ca fiu al celui mai mic element din subarborele drept.
#ifndef ARBORE_H #define ARBORE_H // un nod din arbore struct NodArbore { // informatia utila TipArbore Date; // legaturile catre subarbori NodArbore *Stanga, *Dreapta; // constructor pentru initializarea unui nod nou NodArbore(TipArbore date, NodArbore *stanga = NULL, NodArbore *dreapta = NULL) : Date(date), Stanga(stanga), Dreapta(dreapta){} }; // Arborele este manipulat sub // forma unui pointer catre radacina typedef NodArbore* Arbore; // Creaza un arbore vid Arbore ArbCreare() { return NULL; } // Testeaza daca un arbore este vid bool ArbEGol(Arbore& arbore) { return arbore == NULL; } // Adauga un element intr-un arbore de cautare void ArbAdauga(Arbore& arbore, TipArbore date) { // Cazul 1: arbore vid if (ArbEGol(arbore)) { arbore = new NodArbore(date); return; } // Cazul 2: arbore nevid if (date < arbore->Date) // daca exista subarborele stang if (arbore->Stanga != NULL) // inseram in subarbore ArbAdauga(arbore->Stanga, date); else // cream subarborele stang arbore->Stanga = new NodArbore(date); if (date > arbore->Date) // daca exista subarborele drept if (arbore->Dreapta != NULL) // inseram in subarbore ArbAdauga(arbore->Dreapta, date); else // cream subarborele drept arbore->Dreapta = new NodArbore(date); } // Functie privata de stergere a unui nod void __ArbStergeNod(Arbore& legParinte) { // salvam un pointer la nodul de sters Arbore nod = legParinte;
// daca avem un subarbore drept if (nod->Dreapta != NULL) { // facem legatura legParinte = nod->Dreapta; // daca avem si un subarbore stang if (nod->Stanga) { // cautam cel mai mic element din subarborele drept Arbore temp = nod->Dreapta; while (temp->Stanga != NULL) temp = temp->Stanga; // si adaugam subarborele stang temp->Stanga = nod->Stanga; } } else // daca avem doar un subarbore stang if (nod->Stanga != NULL) // facem legatura la acesta legParinte = nod->Stanga; else // daca nu avem nici un subnod legParinte = NULL; // stergem nodul delete nod; } // Sterge un nod dintr-un arbore de cautare void ArbSterge(Arbore& arbore, TipArbore date) { // Cazul 1: arbore vid if (ArbEGol(arbore)) return; // Cazul 2: stergere radacina if (arbore->Date == date) { // salvam un pointer la radacina Arbore nod = arbore; // daca avem un subarbore drept if (nod->Dreapta) { // facem legatura arbore = nod->Dreapta; // daca avem si un subarbore stang if (nod->Stanga) { // cautam cel mai mic element din subarborele drept Arbore temp = nod->Dreapta; while (temp->Stanga != NULL) temp = temp->Stanga; // si adaugam subarborele stang temp->Stanga = nod->Stanga; } } else // daca avem doar un subarbore stang if (nod->Stanga != NULL) // facem legatura la acesta arbore = nod->Stanga; else // daca nu avem nici un subnod
arbore = NULL; // stergem vechea radacina delete nod; return; } // Cazul 3: stergere nod in arbore nevid // cautam legatura la nod in arbore // si stergem nodul (daca exista) Arbore nodCurent = arbore; while (true) { if (date < nodCurent->Date) if (nodCurent->Stanga == NULL) break; // nodul nu exista else if (nodCurent->Stanga->Date == date) // nodul de sters este descendentul stang __ArbStergeNod(nodCurent->Stanga); else // continuam cautarea in subarborele stang nodCurent = nodCurent->Stanga; else if (nodCurent->Dreapta == NULL) break; // nodul nu exista else if (nodCurent->Dreapta->Date == date) // nodul de sters este descendentul drept __ArbStergeNod(nodCurent->Dreapta); else // continuam cautarea in subarborele stang nodCurent = nodCurent->Dreapta; } } // Cauta recursiv un nod in arborele de cautare bool Cautare(Arbore& arbore, TipArbore info) { // conditia de oprire din recursie if (arbore == NULL) return false; // verificam daca am gasit nodul if (arbore->Date == info) return true; // daca cheia este mai mica if (arbore->Date < info) // cautam in subarborele stang return Cautare(arbore->Stanga, info); else // altfel cautam in subarborele drept return Cautare(arbore->Dreapta, info); } #endif //ARBORE_H
Parcurgerea unui arbore de cutare n ordinea stnga rdcin dreapta conduce la obinerea listei nodurilor n ordinea cresctoare a cheilor. Funcia urmtoare afieaz n ordine elementele unui arbore binar de cutare:
void AfisareSRD(Arbore& arbore) { if (ArbEGol(arbore))
Parcurgerea nerecursiv a unui arbore binar de cutare se poate face folosind o stiv:
void TraversareNerecursiv(Arbore& arbore) { Stiva stiva = StCreare(); // a) ne deplasam pana la primul nod Arbore nod = arbore; while (nod != NULL) { StAdauga(stiva, nod); nod = nod->Stanga; } if (!StEGoala(stiva)) cout << StVarf(stiva)->Date << " "; // b) traversam arborele in inordine Arbore parinte, copil; while (!StEGoala(stiva)) { parinte = StExtrage(stiva); copil = parinte->Dreapta; while (copil != NULL) { StAdauga(stiva, copil); copil = copil->Stanga; } if (!StEGoala(stiva)) cout << StVarf(stiva)->Date << " "; } }
Dac n urma unei operaii de adugare sau tergere arborele nu mai este echilibrat (BF {-1,0,1}), acesta trebuie echilibrat. Echilibrarea n cazul arborilor AVL se face prin intermediul operaiei de rotire la stnga (BF>1) sau la dreapta (BF<1). Pivotul n jurul cruia se face rotirea este cel mai de jos nod care are BF {-1,0,1}. Procedeul de rotire continu pn n momentul n care arborele redevine echilibrat. Rotirea se face dup modelul urmtor: 1. rotire la dreapta
2. rotire la stnga
Arbori B
Arborii B (de la Balanced) sunt arbori de cutare echilibrai proiectai pentru lucrul cu volume foarte mari de date (stocate pe suporturi de memorie extern). Proprietile definitorii ale arborilor B sunt: 1. Toate nodurile au urmtoarele cmpuri: a. n numrul de chei stocate n nod b. k1,,kn cheile stocate n nod cu proprietatea k1<k2<<kn c. c1,,cn+1 pointeri la subarbori 2. Toate cheile din subarborele indicat de ci sunt cuprinse ntre ki-1<ki; cheile din subarborele indicat de c1 sunt mai mici dect k1, iar cele din subarborele indicat de cn+1 sunt mai mari dect kn 3. Toate nodurile frunz se afl la acelai nivel h 4. Fiecare arbore B are asociat un grad t>2. Toate nodurile, cu excepia rdcinii trebuie s aib ntre t-1 i 2t-1 noduri Cele dou avantaje majore ale arborilor B care i recomand pentru folosirea n situaiile n care este necesar prelucrarea unui volum mare de date sunt: permite citirea mai multor chei la un singur acces la disc (gradul t este ales astfel nct dimensiunea unui nod corespunde dimensiunii unei pagini de disc) necesit accesarea a foarte puine pagini pentru a efectua o cutare ( O(logt n) )
Principalele operaii care se efectueaz pe un astfel de arbore sunt cutarea, adugarea de chei i tergerea de chei. Cutarea se face similar cu cutarea ntr-un arbore binar dup urmtorul algoritm:
i = 1 while i <= nod.n and cheie > nod.k[i] i = i + 1 if i <= nod.n and cheie = nod.k[i] return (nod, i) if nod.c[i] != nul return cautare(nod.c[i], cheie) else return nul
// cutam cheia n nodul curent // verificm dac am gsit nodul // // // // dac nu este nod frunz continum cutarea altfel nseamn c avem un nod frunz i oprim cutarea
Adugarea unei chei se face recursiv printr-o singur parcurgere n urmtorii pai: 1. Dac nodul rdcin este plin (are 2t-1 chei), atunci se descompune.
2. Se pornete procedura recursiv care parcurge arborele ca la cutare i execut urmtoarele aciuni pentru fiecare nod: a. Dac nodul este nod frunz se insereaz cheia. b. Dace nu este nod frunz: i. Dac nodul copil este plin se descompune. ii. Se apeleaz procedura pentru nodul copil. Operaia de descompunere a unui nod cu 2t-1 chei presupune gsirea elementului median al nodului, mutarea acestuia n nodul printe sau crearea unui nod nou n cazul rdcinii i descompunerea nodului iniial n dou noduri cu t-1 chei. Exemplu de descompunere:
a) nodul iniial
b) nodul descompus
Se observ c toate inserrile se fac ntr-un nod frunz, iar arborele crete n sus, de la rdcin, prin intermediul operaiei de descompunere. Pentru exemplificare vom considera urmtorul arbore B de grad t=2 (fiecare nod cu excepia rdcinii va avea ntre 2 i 5 chei) cu dou nivele:
a) arborele iniial
12
23
41
60
72
17
19
45
48
49
85
96
98
26
35
65
66
70
d) arborele dup inserarea elementului 36 (descompunere nod rdcin) tergerea unei chei se face similar cu adugarea. Pstrarea proprietilor arborelui B n urma tergerii unei chei se face prin dou mecanisme: coborrea unei chei din nodul printe/fiu n cazul n care acesta are cel puin t chei sau este nodul rdcin recompunerea nodului rdcin n situaia n care nodul rdcin are 1 cheie i nodurile copil au cte t-1 chei (astfel se realizeaz scderea nlimii arborelui)
5. Probleme
1. Scriei funcia pentru numrarea elementelor dintr-un arbore oarecare. 2. Scriei funcia pentru transformarea unui arbore oarecare ntr-o list simplu nlnuit prin parcurgerea acestuia. 3. Scriei funciile pentru determinarea elementului minim i elementului maxim dintrun arbore binar de cutare. 4. Scriei funcia pentru afiarea n ordine a elementelor dintr-un arbore binar de cutare. 5. Scriei o funcie iterativ pentru cutarea unui element ntr-un arbore de cutare. 6. Scriei funcia de concatenare a doi arbori binari de cutare (funcia de adugare n arbore se presupune creat n prealabil). 7. Scriei funcia pentru determinarea celui mai apropiat printe comun a dou noduri dintr-un arbore binar. 8. Precizai cum va arta un arbore AVL dup introducerea cheilor 12, 8, 5, 77, 12, 88, 92, 93, 94, 95, 7, 8, 9.
9. Precizai cum va arta un arbore B de grad 3 dup introducerea cheilor 12, 8, 5, 77, 12, 88, 92, 93, 94, 95, 7, 8, 9. 10. O societate de investiii deine o baz de aproximativ 8 000 000 clieni. Precizai ce structur de date trebuie folosit pentru a permite o regsire rapid a clienilor pe baza codului numeric personal i argumentai alegerea.