Table of Contents

Laborator 7 - ABC și Heap

Responsabili

Obiective

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

Noțiuni teoretice - ABC

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ă caracteristică a arborilor de căutare, este aceea că parcurgerea inordine produce o secvență ordonată crescător a cheilor din nodurile arborelui.

Valoarea maximă

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

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 căutare(nod, cheie) {
  if nod == NULL
    return false;
  if nod.cheie == cheie
    return true;
 
  if cheie < nod.cheie
    return căutare(nod.stanga, cheie);
  else
    return căutare(nod.dreapta, cheie);
}

Inserarea

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

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

  • Ștergerea unui nod frunză sau a unui nod cu un singur copil dintr-un arbore binar de căutare este o operație simplă. Pe lângă eliminarea efectivă a nodului, este necesară și actualizarea referinței din nodul părinte, astfel încât aceasta să pointeze către copilul nodului șters (dacă există) sau către NULL, în cazul în care nodul șters era o frunză.
  • Eliminarea unui nod cu doi succesori este mai complexă, deoarece trebuie menținută proprietatea specifică arborilor binari de căutare (BST). În acest caz, nodul de șters este înlocuit cu un nod care are o valoare apropiată, astfel încât structura și regulile arborelui să rămână corecte. O soluție frecvent utilizată este înlocuirea cu nodul care are cea mai mică valoare din subarborele drept (adică nodul cel mai din stânga din acel subarbore). Acest nod este garantat a fi mai mare decât toate valorile din subarborele stâng și mai mic decât celelalte valori din subarborele drept, păstrând astfel ordinea specifică unui arbore binar de căutare.

Complexitatea operațiilor (căutare, inserare, ștergere) într-un arbore binar de căutare este - pe cazul mediu - O(log n).

Noțiuni teoretice - Heap

Mai sus 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, 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:

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

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

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.
  • 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ă când 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:

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.
push(heap, X)
{
    heap[dimVec] = X;
    dimVec++;
    pushUp(heap, 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.
extractMin(heap)
{
    interschimba(heap[0], heap[dimVec - 1]);
    dimVect--;
    pushDown(heap, 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.

Se poate implementa inserând, 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(heap) 
{
    heap = 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(heap, 0);
    }
}

ABC vs Heap

Deși la prima vedere nu există mari diferențe între cele două structuri de date, ele sunt complet diferite. Se poate observa că ele diferă atât la nivelul implementării (abc:pointeri către fii vs heap:vector), cât și al complexităților operațiilor specifice. Totuși, deși ambele se pot folosi în rare cazuri pentru același scop (fără a fi la fel de eficiente), ele au întrebuințări diferite.

ABC

HEAP

Un arbore este echilibrat dacă pentru fiecare nod din arbore diferența în valoare absolută dintre înălțimile subarborilor acestuia este cel mult 1.

Exerciții

Trebuie să vă creați cont de Devmind, dacă nu v-ați creat deja, pe care îl veți folosi la SD pe toată durata semestrului.

1) [3.5p] BST

Task1 va testa operațiile de insert, remove și afișarea (în ordine) a elementelor introduse în BST.

2) [3.5p] Heap

Task2 va abstractiza următoarea problemă:

Avem un clasament în care pot fi introduse echipe - nume + scor.

Trebuie să efectuăm N operații de tipul:

La final, vom afișa topul echipelor rămase în joc.

3) [BONUS 2p] BST K-Smallest

Se dă un arbore binar de căutare cu valori de tip întreg. Să se întoarcă a k-a cea mai mică valoare din arbore.

Bibliografie