Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sd-ca:laboratoare:laborator-11 [2014/04/28 15:19]
andrei.parvu [Referințe]
sd-ca:laboratoare:laborator-11 [2015/05/21 05:57] (current)
mihai.neacsu2901 [Aplicaţii]
Line 1: Line 1:
-====== Laborator 11 - Arbori binari de căutare echilibrațiTreapuri. ======+====== Laborator 11 - Heap-uri ​ ====== 
 +Responsabili:​ 
 +  * [[mihai.mneacsu@gmail.com|Mihai Neacşu]] 
 +  * [[petrisor_cosmin_ioan@yahoo.com|Cosmin Petrişor]] 
 +====Obiective ​=====
  
-Responsabil+În urma parcurgerii acestui laborator, studentul va fi capabil să
-  * [[andrei.parvu@cti.pub.ro | Andrei Pârvu]] (20132014)+  * înţeleagă diferitele moduri de reprezentare a arborilor;​ 
 +  * definească proprietăţile structurii de heap; 
 +  * implementeze operaţii de inserare, ştergere şi căutare care să păstreze proprietatea de heap; 
 +  * folosească heap-ul pentru a implementa o metodă de sortare eficientă. 
 +===== Moduri de reprezentare a arborilor ===== 
 +În laboratorul precedent am considerat arborii binari ca fiind o înlănţuire de structuri, legate între ele prin pointeri la descendenţii stâng, respectiv dreptAceastă 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 minimCu toate acesteametoda 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:
 +  *Înlocuirea pointer-ilor din structurile asociate nodurilor cu întregi ce reprezintă indici într-un vector de astfel de structuri. Primul element din vector va fi rădăcina arborelui, şi va exista un contor curent (la nivelul întregului vector) care indică următoarea poziţie liberă. Atunci când un nod trebuie adăugat în arbore, i se va asocia valoarea curentă a contorului, iar acesta va fi incrementat. În nodul părinte se va reţine indicele **în vector** al noului nod, în locul adresei lui în memorie (practic acesta este un mic mecanism de alocare de memorie, pe care îl gestionăm noi).
 +  *Eliminarea totală a informaţiei legate de predecesori,​ şi folosirea unei formule de calcul a părintelui si a descendenţilor unui nod pe baza indicelui acestuia în vector.
  
-===== Obiective =====+Pentru un arbore binar, cea de-a doua modalitate se implementează conform figurii de mai jos:
  
-În urma parcurgerii acestui laborator studentul va+{{:sd-ca:​laboratoare:​heap.jpg}}
  
-  * înțelege conceptul unui arbore echilibrat de căutare 
-  * exemplifica acest concept pe structura de treap 
-  * implementa operațiile de adăugare nod, ștergere nod și rotiri ​ 
-  * face operații mai complexe si parcurgri de treapuri 
  
 +Se consideră că arborele este aşezat în vector în ordine (începând de la 0) de la primul nivel până la ultimul, iar nodurile fiecărui nivel se aşează de la stânga la dreapta. Poziţiile fiecărui nod în nivel se consideră ca şi când arborele ar fi complet (iar nodurile lipsă sunt ignorate).
  
-===== Necesitatea structurii ​de arbore binar de căutare echilibrat =====+Reprezentarea liniara (sub formă ​de vector) pentru un arbore binar complet devine:
  
-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 programatoruluiDe 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.+{{:​sd-ca:​laboratoare:​2000px-binary_tree_in_array.svg.png?400}}
  
-În laboratoarele precedente am observat că un arbore binar de utare de înalțime ''​h''​ implementează operațiile descrise mai sus într-o complexitate de ''​O(h)''​. Dacă acest arbore binar nu este capabil de a gestiona elementele ce sunt inserate ​pentru ​a îșmenține o structura echilibrată atunci complexitatea pe operațiile de baza va crește. Exemplusă presupunem ca avem de introdus ''​n''​ numere intr-un arbore binar de căutare; întamplarea face ca numerele să fie sortatede unde rezultă că arborele format va fi liniar ​(fiecare nod va avea maxim doi vecini); astfelcomplexitatea pe operatiile de baza va fi ''​O(n)''​ la fel ca în cazul folosirii unui simplu vector.+Se constată că poziţia nodului rădăcină în vector ​este 0, iar pentru ​fiecare nod în parte, părintele şdescendenţii se pot calcula după formulele: 
 +  * Parinte(i) = (i 1) / 2, unde i este indicele nodului curent 
 +  * IndexStanga(i= 2 * i + 1unde i este indicele nodului curent 
 +  * IndexDreapta(i= 2 * i + 2, unde i este indicele nodului curent 
 +=====Proprietăţi ale structurii de heap binar. Operaţii elementare.===== 
 +Î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.
  
-===== Noțiuni de bază despre treapuri =====+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.
  
-Treapurile sunt unii din arborii 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. +Într-o enunțare echivalentă:
-Fiecare nod din treap va retine două câmpuri: +
-  * cheia - informația care se reține în arbore și pe care se fac operațiile de inseare, căutare și ștergere +
-  * prioritatea - un număr pe baza căruia se face echilibrarea arborelui+
  
-Această structură trebuie să respecte doi invarianți:​ +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 tuturor descendenților săi.
-  * Invariantul de arbore ​de cautare (search **tr**ee) - **cheia** unui nod va fi mai mare sau egală decât **cheia** fiului stânga (dacă există) si mai mică sau egală decât ​**cheia** fiului dreapta (dacă există); cu alte cuvinte o parcurgere in inordine ​arborelui va genera șirul sortat de chei. +
-  * Invariantul de h**eap** - **prioritatea** unui nod este mai mare sau egală decât **prioritățile** fiilor.+
  
-Astfel, se poate observa ​că numele structurii de date a venit din acești doi invarianți:​ tr-eap.+<​code ​c>​H[Parinte(x)] <= H[x]</​code>​
  
-Cum se menține echilibrul structurii? De fiecare dată când un nod este inserat în arbore prioritatea lui este generată random (o metodă similară cu cea de la randomized quick sortîn care la fiecare pas pivotul este generat aleator) - astfel arborele va fi aranjat într-un mod aleator (bineînțeles,​ respectând cei doi invarianți)cum numărul arborilor echilibrați este mai mare decât cel al arborilor rău echilibrați,​ șansa este destul de mică ca prioritățile generate aleator să nu mențină arborele echilibrat. +**H[x]** reprezintă valoarea nodului **x**din vectorul H asociat arborelui.
-Demonstratia complet teoretică asupra faptului că operațiile de baza au complexitatea O(logN) se poate găsi in 2.+
  
 +În mod similar, un max-heap are semnul inegalităţii inversat. Astfel, putem defini şi recursiv proprietatea de heap pentru orice (sub)arbore:​
 +  * nodul rădăcină trebuie să respecte proprietatea de heap (inegalitatea);​
 +  * cei doi subarbori descendenţi sa fie heap-uri.
  
-===== Structura unui nod =====+Pentru a implementa operaţiile de inserare, ştergere, etc. pentru un heap, vom avea nevoie mai întâi de două operaţii elementare:​ 
 +  *''​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.
  
-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.+  *''​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.  
 +===== Operaţii uzuale asupra heap-ului ===== 
 +Având implementate cele două operaţii ​de bază, putem defini operaţiile uzuale ​de manipulare a heap-urilor:
  
-<code c+++==== Peek ==== 
-template <​typename T> struct Treap +Operația întoarce valoarea minimă din min-heap. Valoarea se va afla la indexul 0 al vectorului de implementare a heap-ului. 
-  T key+ 
-  int priority+==== Push (insert) ==== 
-  ​Treap<​T>​ *left, *right+ 
-};+Adaugă o nouă valoare la heap, crescându-i astfel dimensiunea cu 1. 
 +   
 +Algoritmul pentru această funcție este următorul:​ 
 +  - introducem elementul de inserat pe prima poziție liberă din vectorul de implementare a heap-ului (în principiu ''​dimVect''​);​ 
 +  - "​împingem"​ elementul adăugat în vector până la poziția în care se respectă proprietatea de heap; veți folosi funcția ''​pushUp''​. 
 + 
 +Pseudocod:​ 
 + 
 +<code c> 
 +push(X) 
 +
 +    ​heap[dimVec] = X
 +    ​dimVec++
 +    ​pushUp(dimVec - 1)
 +}
 </​code>​ </​code>​
  
-Bineînțeles, tipul de date trebuie să permită o relație de ordine totală astfel încât oricare două elemente ​să poată fi comparate.+==== Pop (extractMin) ==== 
 +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: 
 +  - elementul minim din heap (de pe prima poziție) va fi interschimbat cu elementul ​de pe ultima poziție a vectorului;​ 
 +  - dimensiunea vectorului va fi redusă cu 1 (pentru a ignora ultimul element, acum cel pe care doream ​-l înlăturăm) 
 +  - vom "​împinge"​ nodul care se afla acum în rădăcina heap-ului către poziția în care trebuie sa fie pentru a fi respectată proprietatea de heap; acest lucru se va face cu funcția ''​pushDown''​.
  
-===== Operații de bază =====+Pseudocod:
  
-Mai jos este descris pseudocodul pentru operațiile de bază făcute cu treapuri.\\ +<code c> 
-Pentru exemplificarea operațiilor am folosit un nod specialnumit ''​nil'',​ care reprezintă un nod fictiv, ce nu reține date, folosit pentru a arăta că nu există un nod efectiv în treap. De exemplu, dacă un nod ''​x''​ are ambii fii egali cu ''​nil''​ înseamnă ca ''​x''​ este frunză în arbore.+extractMin() 
 +
 +    interschimba(heap[0]heap[dimVec - 1]); 
 +    dimVect--;​ 
 +    pushDown(0);​ 
 +
 +</​code>​
  
-==== Căutarea ​====+=====Algoritmul Heap Sort ===== 
 +Î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. Există mai multe metode de a implementa această sortare, dintre care prezentăm doua dintre ele:
  
-Căutarea ​se face exact ca la un arbore binar de căutare.+  - Se inserează, 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. 
 +  - Se implementează funcţiile din secţiunile precedente pentru ​un max-heap, şi apoi se foloseşte următorul algoritm (în pseudocod):
  
-<​code>​ +<​code ​c
-bool cautare(nod, cheie) { +HeapSort()  
-  if nod == nil +
-    ​return false+    ​ConstruiesteMaxHeap()
-  if nod.cheie ​== cheie +    for (i=dimHeap-1; i>=1; i--)  
-    ​return true; +    ​{ 
-   +        // Punem maximul la sfarsitul vectorului 
-  if cheie < nod.cheie +        ​interschimba(heap[0]heap[i]); 
-    return cautare(nod.stangacheie); +        // '​Desprindem'​ maximul de heap (valoarea ramanand astfel in pozitia finala) 
-  else +        ​dimHeap--;​ 
-    ​return cautare(nod.dreapta,​ cheie);+        // Reconstituim heap-ul ramas 
 +        pushDown(0); 
 +    }
 } }
 </​code>​ </​code>​
 +===== Aplicaţii =====
 +Porniți exercițiile de la {{:​sd-ca:​laboratoare:​labheap-tasks.zip | scheletul de cod}} oferit.
 +Modificați **main.cpp**,​ adăugând și testând toate operațiile din clasa Heap.
  
 +1. [1p] **heap.h** Definiţi o structură de vector pe care să poată fi folosite operaţiile de heap-uri, şi funcţii de construcţie şi eliberare a structurii:
  
-==== Inserarea ====+  *[0.5p] Constructor pentru inițializarea unui heap. ''​capVect''​ reprezintă numărul maxim de elemente din vector. Codul va trebui să aloce memorie separată şi apoi să lucreze cu acea memorie.
  
-Inserarea unui nod se face generand o prioritate aleatoare pentru acesta și procedând asemănător ca pentru un arbore de căutare, adăugând nodul la baza arborelui printr-o procedură recursivă, pornind de la rădăcina acestuia.+<​code ​c++> 
 +template <​typename T> 
 +Heap<​T>::​Heap(int capVect) 
 +
 +    // TODO 1.
 +
 +</​code>​
  
-Deși inserarea menține invariantul arborelui de căutare, invariantul de heap poate să nu se mai respecteDe 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.+  *[0.5p] Funcție pentru eliberarea memoriei alocate pentru values.
  
-Mai jos avem pseudocodul pentru ​operația de inserare.+<code c++> 
 +template <​typename T> 
 +Heap<​T>::​~Heap() 
 +
 +    // TODO 1.2 
 +
 +</​code>​ 
 + 
 +2. [3p] Implementaţi ​operaţiile elementare ​de lucru cu heap-uri, prezentate în secţiunile anterioare:​ 
 + 
 +  *[1p] Implementati functiile de calcul ai parintelui si ai descendentilor. 
 + 
 +<code c++> 
 +template <​typename T> 
 +int Heap<​T>::​parent(int poz) 
 +
 +    // TODO 2.1 
 +
 + 
 +template <​typename T> 
 +int Heap<​T>::​leftSubtree(int poz) 
 +
 +    // TODO 2.1 
 +
 + 
 +template <​typename T> 
 +int Heap<​T>::​rightSubtree(int poz) 
 +
 +    // TODO 2.1 
 +
 +</​code>​ 
 + 
 +Cele trei funcţii de mai sus vor întoarce -1 în cazul în care părintele, respectiv descendenţii nu există. 
 + 
 +  * [2p] Implementati pushUp si pushDown. 
 +<code c++> 
 +template <​typename T> 
 +void Heap<​T>::​pushUp(int poz) 
 +
 +    // TODO 2.
 +     
 +}
  
-<code+template ​<typename T
-void insert(nod, cheie, prioritate) { +void Heap<T>::​pushDown(int poz
-  if nod == nil +{ 
-    nod = creza nou nod pe baza de cheie si prioritate +    ​// TODO 2.2
-  else if cheie nod.cheie +
-    insert(nod.stanga, cheie, prioritate+
-  else +
-    ​insert(nod.dreapta, cheie, prioritate)+
     ​     ​
-  if nod.stanga.prioritate > nod.prioritate 
-    rotireDreapta(nod) 
-  else if nod.dreapta.prioritate > nod.prioritate 
-    rotireStanga(nod) 
 } }
 </​code>​ </​code>​
  
- Spre exemplu, dacă am dori să inserăm nodul cu cheia 9 si prioritatea 51, pașii vor arată în felul urmator: 
  
-{{ :sd-ca:laboratoare:​inserare.png }}+3. [1p] Implementaţi operaţiile uzuale de lucru cu heap-uri:
  
-Se observă necesitatea rotirilor pentru a aduce nodul nou inserat în vârful arborelui ​(are prioritatea cea mai mare).+<code c++> 
 +template <​typename T> 
 +void Heap<​T>::​insert(T x) 
 +
 +    // TODO 3 
 +}
  
-Cele două tipuri de rotiri sunt prezentate vizual în imaginea de mai jos:+template <​typename T> 
 +T Heap<​T>​::peek() 
 +
 +    // TODO 3 
 +}
  
-{{ :sd-ca:laboratoare:​rotire.png }}+template <​typename T> 
 +T Heap<​T>​::extractMin() 
 +
 +    // TODO 3 
 +} 
 +</​code>​
  
 +4. [2p] **p4.cpp** Implementaţi algoritmul de sortare folosind heap-uri, alegând una dintre cele două metode prezentate mai sus. Testați implementarea voastră a sortării rulând scriptul de testare ''​test.sh''​.
  
-==== Ștergerea ====+**Obs.:**  
 +  * Se va citi întâi numărul **n** de elemente, iar apoi **n** numere care trebuie sortate. 
 +  * Citirea se face de la **stdin**. Nu modificați afișarea! Afișarea are formatul folosit pentru script-ul de testing. 
 +**BONUS!** [1p] Implementaţi şi celelalta metoda de sortare prin heap-uri, în afară de cea aleasă iniţial. Pentru testare se va modifica doar funcția ''​heapSort''​ și se va executa tot scriptul ''​test.sh''​.
  
-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 arboreluiiar 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.+<​hidden>​ 
 +2. [3p] Implementaţi ​operaţiile elementare ​de lucru cu heap-uriprezentate ​în secţiunile anterioare:
  
-<​code>​ +  *[1p] Implementati functiile de calcul ai parintelui si ai descendentilor. 
-void sterge(nod, cheie{ + 
-  if nod == nil +<​code ​c++
-    ​return +template <​typename T> 
-  +int Heap<​T>::​parent(int poz
-  if cheie nod.cheie +{ 
-    ​sterge(nod.stanga, cheie+    ​// TODO 2.1 
-  else if cheie > nod.cheie +} 
-    ​sterge(nod.dreapta, cheie) + 
-  else if nod.stanga == nil si nod.dreapta == nil +template ​<typename T> 
-    ​sterge nod +int Heap<​T>::​leftSubtree(int poz
-  else if nod.stanga.prioritate ​nod.dreapta.prioritate +{ 
-    ​rotireDreapta(nod+    ​// TODO 2.1 
-    ​sterge(nod,​ cheie); +} 
-  else + 
-    ​rotireStanga(nod) +template <​typename T
-    sterge(nod, cheie)+int Heap<​T>::​rightSubtree(int poz
 +{ 
 +    ​// TODO 2.1
 } }
 </​code>​ </​code>​
  
-===== Exerciții =====+Cele trei funcţii de mai sus vor întoarce -1 în cazul în care părintele, respectiv descendenţii nu există.
  
-Pentru exerciții porniți de la {{:sd-ca:laboratoare:​lab11-schelet.zip|acest}} schelet de laborator.+  * [2p] Implementati pushUp si pushDown. 
 +<code c++> 
 +template <​typename T> 
 +void Heap<​T>​::pushUp(int poz) 
 +
 +    // TODO 2.
 +     
 +}
  
-<hidden+template ​<typename T
-  - Implementați funcțiile de bază pentru un treap+void Heap<​T>​::​pushDown(int poz) 
-    ​* [1p] Căutare +{ 
-    ​[1p] Rotiri stânga șdreapta +    ​// TODO 2.2 
-    ​* [1p] Inserare +    ​ 
-    ​* [1p] Ștergere +
-  ​- ​[2p] Realizațo parcurgere a treapului astfel încât să obțineți cheie sortate crescător+</​code>​ 
-  ​- [2p] Realizați o parcurgere a treapului astfel încât să obțineți o structură arborescentă a priorităților, ​pentru ​a observa invariantul ​de heap+ 
-  ​- ​[4pScriețo funcție care să răspundă într-o complexitate de ''​O(logN)''​ la următoarea cerință: Care este cea de-a K-a cheie, în ordinea sortării crescătoare, care se află în treap.+ 
 +3. [1p] Implementaţoperaţiile uzuale de lucru cu heap-uri: 
 + 
 +<code c++> 
 +template <​typename T> 
 +void Heap<​T>::​insert(T x) 
 +{ 
 +    ​// TODO 3 
 +
 + 
 +template <​typename T> 
 +T Heap<​T>::​peek() 
 +{ 
 +    ​// TODO 3 
 +
 + 
 +template <​typename T> 
 +T Heap<​T>::​extractMin() 
 +
 +    // TODO 3 
 +
 +</​code>​ 
 + 
 +4. [2p] **p4.cpp** Implementaţalgoritmul de sortare folosind heap-uri, alegând una dintre cele două metode prezentate mai sus. Testați implementarea voastră a sortării rulând scriptul de testare ''​test.sh''​. 
 + 
 +**Obs.:​** ​ 
 +  ​* Se va citi întâi numărul **n** de elemente, iar apoi **n** numere care trebuie sortate. 
 +  * Citirea se face de la **stdin**. Nu modificați afișarea! Afișarea are formatul folosit ​pentru ​script-ul ​de testing
 +**BONUS!** ​[1pImplementaţşi celelalta metoda ​de sortare prin heap-uri, în afară de cea aleasă iniţial. Pentru testare ​se va modifica doar funcția ''​heapSort''​ și se va executa tot scriptul ''​test.sh''​.
 </​hidden>​ </​hidden>​
 +===== Resurse =====
 +[1] [[http://​www.cplusplus.com | C++ Reference]]
 +
 +[2] Thomas H. Cormen, Charles E. Leiserson, Ronald R. Rivest -"​Introducere în Algoritmi"​ (Capitolul 7 - Heapsort)
  
 +[3] [[http://​en.wikipedia.org/​wiki/​Heap_(data_structure)|Heap Data Structure]]
  
-===== Referințe =====+[4] [[http://​en.wikipedia.org/​wiki/​Binary_heap|Binary Heap]]
  
-  * [[http://​www.cs.cmu.edu/​afs/​cs.cmu.edu/​project/​scandal/​public/​papers/​treaps-spaa98.pdf | Fast Set Operations Using Treaps]] +[5] [[http://en.wikipedia.org/wiki/Heap_sort|Heap Sort]]
-  * [[http://compgeom.cs.uiuc.edu/~jeffe/teaching/​algorithms/​notes/​10-treaps.pdf | Randomized Binary Search Trees]] +
-  * [[http://​www.cs.cmu.edu/​afs/​cs.cmu.edu/​academic/​class/​15451-s07/​www/​lecture_notes/​lect0208.pdf ​Balanced Search Trees]]+
sd-ca/laboratoare/laborator-11.1398687544.txt.gz · Last modified: 2014/04/28 15:19 by andrei.parvu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0