Responsabili
În urma parcurgerii articolului, studentul va fi capabil să:
O structură de date este o metodă de a reține anumite date astfel încât operațiile cu acestea (căutare, inserare, ștergere) să fie făcute cât mai eficient și să respecte cerințele programatorului. De multe ori, o anumită structură de date se află la baza unui algoritm sau sistem, iar o performanță bună a acesteia (complexitate spațială și temporală cât mai mică) influențează performanța întregului sistem.
În laboratoarele precedente am observat că un arbore binar de căutare de înălțime h implementează operațiile descrise mai sus într-o complexitate de O(h). Dacă acest arbore binar nu este capabil să gestioneze elementele ce sunt inserate pentru a își menține o structura echilibrată atunci complexitatea pe operațiile de bază va crește.
Treapurile sunt un bun exemplu de arbori de căutare echilibrați, cel mai des folosiți datorită implementării relativ ușoare (comparativ cu alte structuri similare cum ar fi Red-Black Trees, AVL-uri sau B-Trees), dar și a modului de operare destul de intuitiv. Fiecare nod din treap va reţine două câmpuri:
Această structură trebuie să respecte doua proprietati (sau invarianți):
Se poate observa că numele structurii de date provine din acești doi invarianți: tr-eap.
Demonstraţia teoretică asupra faptului că operațiile de bază au complexitatea O(logN) se poate găsi aici[0].
Mai jos avem codul pentru structura nodului unui treap; se pot observa asemănările cu structura de arbore binar și cu cea de heap.
typedef struct treap_node_t treap_node_t; struct treap_node_t { /* left child */ treap_node_t *left; /* right child */ treap_node_t *right; /* priority that will be randomly set at insertion */ int priority; /* the key of the node which will also be used for sorting */ void *key; }; typedef struct treap_t treap_t; struct treap_t { /* root of the tree */ treap_node_t *root; /* function used for comparing the keys */ int (*cmp)(const void *key1, const void *key2); };
Observați că Treap-ul conține un pointer la o funcție de tip int. Aceea va fi funcția folosită pentru compararea cheilor, care vă fi oferită funcției ce creează arborele.
Mai jos este descris pseudocodul pentru operațiile de bază făcute cu treapuri.
Deoarece treapul respecta proprietatea de arbore binar de cautare, căutarea se face exact ca la acesta. Vezi laboratorul 9.
Inserarea unui nod se face generând o prioritate aleatoare pentru acesta și procedând asemănător ca pentru un arbore binar de căutare, adăugând nodul la baza arborelui printr-o procedură recursivă, pornind de la rădăcină.
Deși inserarea menține invariantul arborelui de căutare, invariantul de heap poate să nu se mai respecte. De aceea, trebuie definite operații de rotire (stânga sau dreapta), care să fie aplicate unui nod în cazul în care prioritatea sa este mai mare decât ce a părintelui său.
Mai jos avem pseudocodul pentru operația de inserare.
insert(nod, cheie, prioritate) { // Daca gasim o frunza, inseram valoarea dorita la acea pozitie if nod == NULL nod = creeaza nou nod pe baza de cheie si prioritate return nod if cheie < nod.cheie nod.stanga = insert(nod.stanga, cheie, prioritate) // Subarborele drept nu a fost modificat, deci verificam schimbarile din stanga // Asiguram pastrarea proprietatii de heap if nod.stanga.prioritate > nod.prioritate rotireDreapta(nod) else nod.dreapta = insert(nod.dreapta, cheie, prioritate) // Subarborele stang nu a fost modificat, deci verificam schimbarile din dreapta // Asiguram pastrarea proprietatii de heap if nod.dreapta.prioritate > nod.prioritate rotireStanga(nod) }
Spre exemplu, dacă am dori să inserăm nodul cu cheia 9 şi prioritatea 51, pașii vor arată în felul următor:
Cele două tipuri de rotiri sunt prezentate vizual în imaginea de mai jos:
Operația de ștergere este inversul operației de inserare și se aseamăna foarte mult cu ștergerea unui nod în cadrul unui heap. Nodul pe care îl dorim a fi șters este rotit până când ajunge la baza arborelui, iar atunci este șters. Pentru a menține invariantul de heap, vom face o rotire stânga dacă fiul drept are o prioritate mai mare decât fiul stâng și o rotire drepta în caz contrar.
sterge(nod, cheie) { if nod == NULL return if cheie < nod.cheie sterge(nod.stanga, cheie) else if cheie > nod.cheie sterge(nod.dreapta, cheie) else if nod.stanga == NULL si nod.dreapta == NULL sterge nod else if nod.stanga.prioritate > nod.dreapta.prioritate rotireDreapta(nod) sterge(nod, cheie) else rotireStanga(nod) sterge(nod, cheie) }
1) [5.5p] Implementați următoarele funcții de bază pentru un treap:
2) [1.5p] Realizați o parcurgere a treap-ului astfel încât să obțineți cheile sortate crescător/descrescător.
Ambele task-uri sunt verificate in unica problema din contest.
Această secțiune nu este punctată și încearcă să vă facă o oarecare idee a tipurilor de întrebări pe care le puteți întâlni la un job interview (internship, part-time, full-time, etc.) din materia prezentată în cadrul laboratorului.