Differences

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

Link to this comparison view

sd-ca:laboratoare:laborator-10 [2014/02/14 00:16]
andrei.petre3105 [Push (insert)]
sd-ca:laboratoare:laborator-10 [2015/05/15 08:13] (current)
andrei.vasiliu2211 [Exerciții]
Line 1: Line 1:
-====== Laborator 10 - Heap-uri  ​====== +====== Laborator 10 - Arbori Binari de Căutare====== 
-Responsabili:​ +Responsabili: ​  
-  *[[bogatu.adrian@gmail.com | Adrian Bogatu]] (2013) +  * [[mihai.mneacsu@gmail.com|Mihai Neacşu]] 
-  *[[serbanescu.vlad.nicolae@gmail.com | Vlad Șerbănescu]](2012)+  * [[petrisor_cosmin_ioan@yahoo.com|Cosmin Petrişor]] 
 ===== Obiective ===== ===== Obiective =====
  
-În urma parcurgerii ​acestui laborator, studentul va fi capabil să: +În urma parcurgerii ​laboratorului, studentul va fi capabil să: 
-  * înţeleagă ​diferitele moduri de reprezentare a arborilor;​ +  *înţeleagă ​structura şi proprietățile unui arbore binar de căutare 
-  * definească ​proprietăţile structurii ​de heap; +  *construiască,​ în limbajul C++, un arbore binar de căutare 
-  * implementeze operaţii ​de inserare, ştergere şi căutare ​care să păstreze proprietatea de heap; +  *realizeze ​parcurgere ​structurii ​de date prin mai multe moduri 
-  * folosească heap-ul pentru a implementa ​metodă de sortare eficientă. +  *realizeze diferite operaţii folosind arborii binari ​de căutare
-===== Moduri de reprezentare ​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 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, această metodă 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: +===== Noțiuni teoretice =====
-  *Î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ă din vector. Atunci când un nod trebuie adăugat în arbore, i se va asocia valoarea curentă a contorului, iar acesta va fi incrementat. Nodul părinte va conţine indicele nodului în vector, î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.+
  
-Pentru un arbore binar, cea de-a doua modalitate se implementează conform figurii de mai jos: 
-{{:​sd-ca:​laboratoare:​heap.jpg}} 
  
 +Un arbore binar de căutare este un arbore binar care are în plus următoarele proprietăți: ​
 +  * cheile stocate în noduri (informația utilă) aparțin unei mulțimi peste care există o relație de ordine
 +  * cheia dintr-un nod oarecare este **mai mare** decât cheile tuturor nodurilor din subarborele stâng şi este **mai mică** decât cheile tuturor nodurilor ce compun subarborele drept
  
-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).+Arborii binari de 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.
  
-Reprezentarea liniara (sub formă de vector) pentru un arbore binar complet devine: +O importantă carcteristică a arborilor ​de căutare, este aceea că parcurgerea ''​inordine''​ produce o secvenţă ordonată crescător a cheilor din nodurile arborelui.
-{{:​sd-ca:​laboratoare:​2000px-binary_tree_in_array.svg.png?400}}+
  
-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:​ +**Valoarea maximă** dintr-un arbore binar de utare se află în **nodul din extremitatea dreaptă** şi se determină prin coborârea pe subarborele dreptiar **valoarea minimă** se află în **nodul din extremitatea stângă**, determinarea ​fiind simetrică.
-  * Parinte(i) = (i - 1) / 2unde i este indicele nodului curent +
-  ​IndexStanga(i) = 2 i + 1, unde 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.+
  
-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.+**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: 
 +  * acestea coincid –> elementul a fost găsit 
 +  * elementul căutat este mai mic decât cheia din nodul curent –> căutarea continuă ​în subarborele stâng 
 +  * elementul ​utat este mai mare decât ​cheia din nodul curent -> căutarea continuă în subarborele drept
  
-Într-o enunțare echivalentă:+**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.
  
-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.+**Ș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 utare sunt posibile următoare cazuri: 
 +  * nodul de şters nu există -> operaţia se consideră încheiată  
 +  * nodul de şters nu are succesori -> este o frunză 
 +  * nodul de şters are un singur successor 
 +  * nodul de şters are doi succesori
  
-<code c>H[Parinte(x)] <= H[x]</​code>​+<note tip> 
 +În cazul ştergerii unui nod frunză sau a unui nod având un singur successor, legătura de la părintele nodului de şters este înlocuită prin legătura nodului de şters la succesorul său (**NULL** în cazul frunzelor).
  
-unde H[x] reprezintă valoarea nodului x, din vectorul H asociat arborelui.+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. 
 +</​note>​
  
-În mod similar, un max-heap are semnul inegalităţii inversat. Astfel, putem defini şi recursiv proprietatea de heap pentru orice (sub)arbore+**Complexitatea** operaţiilor (căutare, inserare, ștergereîntr-un ​arbore ​binar de căutare este - //pe cazul mediu// - **O(logn)**. 
-  * nodul rădăcină trebuie să respecte proprietatea ​de heap (inegalitatea)+===== Exemplu =====
-  ​cei doi subarbori descendenţi sa fie heap-uri.+
  
-Pentru a implementa operaţiile de inserare, ştergere, etc. pentru un heap, vom avea nevoie mai întâi de două operaţii elementare:​ +<​code ​c++ BinarySearchTree.h> 
-  *''​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+#ifndef __BINARY_SEARCH_TREE__H 
- +#define __BINARY_SEARCH_TREE__H 
-  ​*''​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 ea propagă acea valoare spre rădăcina arborelui, până cand heap-ul devine valid.  +  
-===== Operaţii uzuale asupra heap-ului ===== +template ​<typename T
-Având implementate cele două operaţii de bază, putem defini operaţiile uzuale de manipulare a heap-urilor:​ +class BinarySearchTree
- +
-==== Peek ==== +
-Operația întoarce valoarea minimă din min-heap. Valoarea se va afla la indexul 0 al vectorului de implementare a heap-ului. +
- +
-==== Push (insert) ==== +
- +
-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+public: 
-    ​dimVec+++    BinarySearchTree()
-    ​pushUp(dimVec - 1); +    ​~BinarySearchTree(); 
-}+  
 +    ​void insertKey(T x)
 +    void removeKey(T x); 
 +    BinarySearchTree<​T>​* searchKey(T x); 
 +    void inOrderDisplay();​ 
 +         
 +private: 
 +    BinarySearchTree<​T>​ *leftNode;​ 
 +    BinarySearchTree<​T>​ *rightNode;​ 
 +    BinarySearchTree<​T>​ *parent; 
 +    T *pData
 +}
 +  
 +#endif // __BINARY_SEARCH_TREE_H
 </​code>​ </​code>​
 +===== Exerciții =====
  
-==== Pop (extractMin) ==== +Acest laborator se va realiza pornind ​de la **{{sd-ca:​laboratoare:​labbst-tasks.zip|scheletul ​de cod}}**.
-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 să-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''​.+
  
-Pseudocod:+Observații privind scheletul de cod: 
 +  * Scheletul citește N numere dintr-un fișier dat ca parametru în linia de comandă. 
 +  * Aceste N numere sunt introduse într-un arbore binar de căutare, funcționalitate pe care voi trebuie sa o implementaţi. 
 +  * Clasa ''​BinarySearchTree''​ conține un membru de tip pointer către T pentru a reţine informaţia utilă şi de asemenea pentru a putea determina mai simplu cazul contrar. Astfel putem verifica dacă un BinarySearchTree conține minim un element (vezi funcția ''​isEmpty''​). 
 +  * Funcția ''​removeKey''​ întoarce adresa noului nod rădăcină,​ dacă s-a șters vechea rădăcină.
  
-<​code ​c> +1. [**3p**] Implementați următoarele funcționalități de bază ale unui arbore binar de căutare: 
-extractMin() +    * [**0.5p**] constructor. ​(TODO 1.1)  
-{ +    ​[**0.5p**destructor. Eliberați toată memoria alocată. (TODO 1.2
-    ​interschimba(heap[0], heap[dimVec - 1]); +    ​* [**0.5p**] adăugare elemente în arbore.(TODO 1.3) 
-    ​dimVect--; +    ​* [**0.5p**] căutare elemente în arbore. (TODO 1.4
-    ​pushDown(0); +    * [**1p**] parcurgere inordine arbore. (TODO 1.5)
-+
-</​code>​+
  
-=====Algoritmul Heap Sort ===== +2. [**2p**] Implementațurmătoarele funcționalitățavansate ale unui arbore binar de căutare: 
-Întrucât operaţiile de extragere a minimului ş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ţiiO 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: +    * [**0.5p**] funcții pentru returnare valoare minimă/maximă din arboreImplementaţeficient, ​ţinând cont de faptul ca arborele binar este unul de căutare(TODO 2.1
- +<code c++
-  - Se inserează, pe rând, în heap, toate elementele din vectorul nesortatApoi într-un alt şir se extrag minimeleNoul şir va conţine vechiul vector sortat. +T findMin ​(); 
-  - Se implementează funcţiile din secţiunile precedente pentru un max-heap, şi apoi se foloseşte următorul algoritm (în pseudocod)+T findMax ​();
- +
-<code c> +
-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);​ +
-    } +
-}+
 </​code>​ </​code>​
-===== Aplicaţii ===== +    ​* [**0.5p**] calculează şreturnează înălțimea unui arboreÎnălțimea unui arbore se calculează adunând 1 la înălțimea ​maximă a subarborelui ​u stâng șa celui drept. (TODO 2.2)
-Porniți exercițiile de la {{:​sd-ca:​laboratoare:​sd_schelet_cod_lab10.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ţ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:​ +
- +
-  *[0.5p] Constructor pentru inițializarea ​unui heap''​capVect''​ reprezintă numărul maxim de elemente din vector. Codul va trebui ​să aloce memorie separată şapoi să lucreze cu acea memorie. +
 <code c++> <code c++>
-template <​typename T> +int findLevels();
-Heap<​T>::​Heap(int capVect) +
-+
-    // TODO 1.1 +
-}+
 </​code>​ </​code>​
- +    ​* [**1p**funcție pentru ​ ​afișarea cheilor (informației utile) din nodurile situate pe un anumit nivel primit ca parametru. Nivel = distanța de la rădăcină la un nod, nivelul rădăcinii fiind 0. (TODO 2.3)
-  ​*[0.5pFuncție pentru ​eliberarea memoriei alocate pentru values. +
 <code c++> <code c++>
-template <​typename T> +void displayLevel(int level);
-Heap<​T>::​~Heap() +
-+
-    // TODO 1.2 +
-}+
 </​code>​ </​code>​
  
-2. [5pImplementaţi operaţiile elementare ​de lucru cu heap-uri, prezentate ​în secţiunile anterioare:+3Implementați următoarele funcționalități avansate ale unui arbore binar de căutare: 
 +    - [**3p**]Definim noţiunea de 'calea de la rădacină la frunze'​ ca fiind o secvenţă de noduri ce începe ​cu nodul rădacină şi coboară spre un nod frunză. Pentru fiecare astfel de cale din arbore însumaţi valorile din noduri. Afişaţi ​în ordine descrescătoare sumele obţinute pentru fiecare cale din arbore de la rădăcină la frunze. Implementaţi eficient, ţinând cont de faptul ca arborele binar este unul de căutare complet. (TODO 3) 
 +<​code>​
  
-  *[1p] Implementati functiile de calcul ai parintelui si ai descendentilor. +               ​15 ​           ​ 
- +             /    \                                    ​ 
-<code c++> +           10      20                                      
-template <​typename T> +           ​      ​\ ​                                   
-int Heap<​T>::​parent(int poz) +          ​4 ​     17   ​23 ​                                 
-+         / \    / ​ ​\ ​   \ 
-    ​// TODO 2.1 +          6  16  19   32
-+
- +
-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>​ </​code>​
  
-Cele trei funcţii de mai sus vor întoarce -1 în cazul în care părintele, respectiv descendenţii nu există.+4. [**3p**] Implementați ​funcția de ștergere a unui element. (TODO 4) 
 +<note important>​ 
 +Trebuie să tratați și cazul în care se va șterge elementul din rădăcină. 
 +</​note>​
  
-  * [4p] Implementati pushUp si pushDown. +<hidden
-<code c+++3. Implementați următoarele funcționalități avansate ale unui arbore binar de căutare
-template <​typename T> +    ​- [**3p**]Definim noţiunea de 'calea de la rădacină la frunze'​ ca fiind o secvenţă de noduri ce începe cu nodul rădacină şi coboară spre un nod frunză. Pentru fiecare astfel de cale din arbore însumaţi valorile din noduri. Afişaţi în ordine descrescătoare sumele obţinute pentru fiecare cale din arbore de la rădăcină la frunze. Implementaţi eficient, ţinând cont de faptul ca arborele binar este unul de căutare complet. (TODO 3) 
-void Heap<​T>​::pushUp(int poz) +<​code>​
-{ +
-    ​// TODO 2.2 +
-     +
-}+
  
-template <​typename T> +               ​15 ​           ​ 
-void Heap<​T>::​pushDown(int poz) +             /    \                                    ​ 
-{ +           10      20                                      
-    // TODO 2.2 +           ​      ​\ ​                                   
-     +          ​4 ​     17   ​23 ​                                 
-}+         / \    /  \    \ 
 +        ​2 ​  ​6 ​ 16  19   32
 </​code>​ </​code>​
  
  
-3. [3p] Implementaţi ​operaţiile uzuale ​de lucru cu heap-uri:+3. Implementați următoarele funcționalități avansate ale unui arbore binar de căutare: 
 +    - [**3p**]Definim noţiunea de 'calea de la rădacină la frunze'​ ca fiind o secvenţă de noduri ce începe cu nodul rădacină şi coboară spre un nod frunză. Pentru fiecare astfel de cale din arbore însumaţi valorile din noduri. Afişaţi în ordine descrescătoare sumele obţinute pentru fiecare cale din arbore de la rădăcină la frunze. ​Implementaţi ​eficient, ​ţinând cont de faptul ca arborele binar este unul de căutare complet. (TODO 3) 
 +<​code>​
  
-<code c++> +               ​15 ​           ​ 
-template <​typename T> +             /    \                                    ​ 
-void Heap<​T>::​insert(T x) +           10      20                                      
-+           ​      ​\ ​                                   
-    ​// TODO 3 +          ​4 ​     17   ​23 ​                                 
-+         / \    / ​ ​\ ​   \ 
- +        ​2 ​  ​6 ​ 16  19   32
-template <​typename T> +
-T Heap<​T>::​peek() +
-{ +
-    // TODO 3 +
-+
- +
-template <​typename T> +
-T Heap<​T>::​extractMin() +
-+
-    // TODO 3 +
-}+
 </​code>​ </​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''​. +</hidden> ​ 
- +          
-**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''​. +
-===== 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]] +
- +
-[4] [[http://​en.wikipedia.org/​wiki/​Binary_heap|Binary Heap]] +
- +
-[5] [[http://​en.wikipedia.org/​wiki/​Heap_sort|Heap Sort]]+
sd-ca/laboratoare/laborator-10.1392329783.txt.gz · Last modified: 2014/02/14 00:16 by andrei.petre3105
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