Table of Contents

Laborator 10 - Heap-uri

Responsabili:

Obiective

În urma parcurgerii acestui laborator, studentul va fi capabil să:

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 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:

Pentru un arbore binar, cea de-a doua modalitate se implementează conform figurii de mai jos: heap.jpg

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).

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:

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.

Î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]

unde 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:

Pentru a implementa operaţiile de inserare, ştergere, etc. pentru un heap, vom avea nevoie mai întâi de două operaţii elementare:

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:

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:

  1. introducem elementul de inserat pe prima poziție liberă din vectorul de implementare a heap-ului (în principiu dimVect);
  2. “î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:

push(X)
{
    heap[dimVec] = X;
    dimVec++;
    pushUp(dimVec - 1);
}

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:

  1. elementul minim din heap (de pe prima poziție) va fi interschimbat cu elementul de pe ultima poziție a vectorului;
  2. dimensiunea vectorului va fi redusă cu 1 (pentru a ignora ultimul element, acum cel pe care doream să-l înlăturăm)
  3. 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:

extractMin()
{
    interschimba(heap[0], heap[dimVec - 1]);
    dimVect--;
    pushDown(0);
}

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:

  1. 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.
  2. Se implementează funcţiile din secţiunile precedente pentru un max-heap, şi apoi se foloseşte următorul algoritm (în pseudocod):
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);
    }
}

Aplicaţii

Porniți exercițiile de la 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:

template <typename T>
Heap<T>::Heap(int capVect)
{
    // TODO 1.1
}
template <typename T>
Heap<T>::~Heap()
{
    // TODO 1.2
}

2. [3p] Implementaţi operaţiile elementare de lucru cu heap-uri, prezentate în secţiunile anterioare:

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
}

Cele trei funcţii de mai sus vor întoarce -1 în cazul în care părintele, respectiv descendenţii nu există.

template <typename T>
void Heap<T>::pushUp(int poz)
{
    // TODO 2.2
 
}
 
template <typename T>
void Heap<T>::pushDown(int poz)
{
    // TODO 2.2
 
}

3. [1p] Implementaţi operaţiile uzuale de lucru cu heap-uri:

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
}

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.

Obs.:

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] C++ Reference

[2] Thomas H. Cormen, Charles E. Leiserson, Ronald R. Rivest -“Introducere în Algoritmi” (Capitolul 7 - Heapsort)

[3] Heap Data Structure

[4] Binary Heap

[5] Heap Sort