În urma parcurgerii articolului, studentul va fi capabil să:
Un arbore binar de căutare este un arbore binar care are în plus următoarele proprietăți:
Arborii binari de căutare permit menţinerea datelor în ordine şi o căutare rapidă a unei chei, ceea ce îi recomandă pentru implementarea de mulţimi şi dicţionare ordonate.
O importantă carcteristică a arborilor de căutare, este aceea că parcurgerea inordine
produce o secvenţă ordonată crescător a cheilor din nodurile arborelui.
Valoarea maximă dintr-un arbore binar de căutare se află în nodul din extremitatea dreaptă şi se determină prin coborârea pe subarborele drept, iar valoarea minimă se află în nodul din extremitatea stângă, determinarea fiind simetrică.
Căutarea unei chei într-un arbore binar de căutare este asemănătoare căutării binare: cheia căutată este comparată cu cheia din nodul curent (iniţial nodul rădăcină). În funcţie de rezultatul comparaţiei apar trei cazuri:
Pseudocod:
bool cautare(nod, cheie) { if nod == NULL return false; if nod.cheie == cheie return true; if cheie < nod.cheie return cautare(nod.stanga, cheie); else return cautare(nod.dreapta, cheie); }
Inserarea unui nod se face, în funcţie de rezultatul comparaţiei cheilor, în subarborele stâng sau drept. Dacă arborele este vid, se creează un nod care devine nodul rădăcină al arborelui. În caz contrar, cheia se inserează ca fiu stâng sau fiu drept al unui nod din arbore.
Ștergerea unui nod este o operaţie puţin mai complicată, întrucât presupune o rearanjare a nodurilor. Pentru eliminarea unui nod dintr-un arbore binar de căutare sunt posibile următoare cazuri:
Eliminarea unui nod cu doi succesori se face prin înlocuirea sa cu nodul care are cea mai apropiată valoare de nodul şters. Acesta poate fi nodul din extremitatea dreaptă a subarborelui stâng (predecesorul) sau nodul din extremitatea stânga a subarborelui drept (succesorul). Acest nod are cel mult un successor.
Complexitatea operaţiilor (căutare, inserare, ștergere) într-un arbore binar de căutare este - pe cazul mediu - O(logn).
Mai sus considerat arborii binari ca fiind o înlănţuire de structuri, legate între ele prin pointeri la descendenţii stâng, respectiv drept. Această reprezentare are avantajul flexibilităţii şi a posibilităţii de a creşte sau micşora dimensiunea arborelui oricât de mult, cu un efort minim. Cu toate acestea, metoda precedentă nu poate fi folosită atunci când este nevoie de o reprezentare compactă a arborelui în memorie (de exemplu pentru stocarea într-un fişier), pentru că acei pointeri nu sunt valizi decât în cadrul programului curent.
Din acest motiv, există câteva moduri de a stoca arborii într-o structura liniară de date (vectori), dintre care:
Pentru un arbore binar, cea de-a doua modalitate se implementează conform figurii de mai jos:
Reprezentarea liniara (sub formă de vector) pentru un arbore binar complet devine:
Se constată că poziţia nodului rădăcină în vector este 0, iar pentru fiecare nod în parte, părintele şi descendenţii se pot calcula după formulele:
În cele ce urmează vom considera un heap ca fiind de fapt un min-heap. Noţiunile sunt perfect similare şi pentru max-heap-uri.
Un min-heap binar este un arbore binar în care fiecare nod are proprietatea că valoarea sa este mai mare sau egală decât cea a părintelui său.
Într-o enunțare echivalentă:
Un min-heap binar este un arbore binar în care fiecare nod are proprietatea că valoarea sa este mai mică sau egală decât cea a tuturor descendenților săi.
h[parinte(x)] <= h[x]
h[x] reprezintă valoarea nodului x, din vectorul h asociat arborelui.
În mod similar, un max-heap are semnul inegalităţii inversat. Astfel, putem defini şi recursiv proprietatea de heap pentru orice (sub)arbore:
pushDown
, care presupune că heap-ul a fost modificat într-un singur nod şi noua valoare este mai mare decât cel puţin unul dintre descendenţi, şi astfel ea trebuie “cernută” către nivelurile de jos, până când heap-ul devine din nou valid.pushUp
, care presupune că valoarea modificată (sau adăugată la sfârşitul vectorului, în acest caz) este mai mică decât părintele, şi astfel se propagă acea valoare spre rădăcina arborelui, până cand heap-ul devine valid.
Având implementate cele două operaţii de bază, putem defini operaţiile uzuale de manipulare a heap-urilor:
Operația întoarce valoarea minimă din min-heap. Valoarea se va afla la indexul 0 al vectorului de implementare a heap-ului.
Adaugă o nouă valoare la heap, crescându-i astfel dimensiunea cu 1.
Algoritmul pentru această funcție este următorul:
dimVect
);pushUp
.push(X) { heap[dimVec] = X; dimVec++; pushUp(dimVec - 1); }
Funcția aceasta scoate valoarea minimă din heap (și reactualizează heap-ul). Poate întoarce valoarea scoasă din heap.
Pentru a face operația de pop
veți urma pașii:
pushDown
.extractMin() { interschimba(heap[0], heap[dimVec - 1]); dimVect--; pushDown(0); }
Întrucât operaţiile de extragere a minimului şi de adăugare/reconstituire sunt efectuate foarte eficient (complexităţi de O(1), respectiv O(log n) ), heap-ul poate fi folosit într-o multitudine de aplicaţii care necesită rapiditatea unor astfel de operaţii. O aplicaţie importantă o reprezintă sortarea, care poate fi implementată foarte eficient folosind heap-uri. Complexitatea acesteia este O(n*log n), aceeaşi cu cea de la quick sort şi merge sort.
Se poate implementa inserand, pe rând, în heap, toate elementele din vectorul nesortat. Apoi într-un alt şir se extrag minimele. Noul şir va conţine vechiul vector sortat.
HeapSort() { ConstruiesteMaxHeap(); for (i = dimHeap - 1; i >= 1; i--) { // Punem maximul la sfarsitul vectorului interschimba(heap[0], heap[i]); // 'Desprindem' maximul de heap (valoarea ramanand astfel in pozitia finala) dimHeap--; // Reconstituim heap-ul ramas pushDown(0); } }