Subiectul I, cerința 1
Structura de date de tip heap
Definiția ansamblului heap și reprezentarea lui într-un vector, inserarea unei chei și eliminarea nodului cu cheia maximă, urmărite pe un ansamblu cu zece noduri, și o problemă rezolvată cu operațiile specifice.
Definiția și proprietățile
Ansamblul heap este un arbore binar cu două însușiri:
- arborele este aproape complet, adică are toate nivelurile complete, în afară de ultimul, care se completează de la stânga spre dreapta;
- cheile fiecărui părinte și ale fiilor lui respectă o relație de ordine prestabilită.
Structura poartă și numele de arbore de selecție sau de arbore parțial ordonat. Relația de ordine aleasă dă două feluri de ansambluri:
- la heap-ul maxim, fiecare fiu are cheia cel mult egală cu a părintelui;
- la heap-ul minim, fiecare fiu are cheia cel puțin egală cu a părintelui.
Din definiție decurg următoarele proprietăți:
- Relația de ordine se transmite de-a lungul fiecărui drum spre frunze. La un heap maxim, niciun nod dintr-un subarbore nu depășește deci cheia rădăcinii acelui subarbore, iar rădăcina întregului arbore păstrează cheia cea mai mare. La heap-ul minim, ea păstrează cheia cea mai mică.
- Cheile se pot repeta.
- Frații nu se compară între ei, fiindcă relația de ordine îi leagă numai pe părinte și pe fiii lui. De aici vine numele de arbore parțial ordonat.
- Un ansamblu cu n noduri are înălțimea [log₂n], ca orice arbore aproape complet.
- Pe extragerea repetată a rădăcinii se bazează coada cu priorități: cheile ies din structură în ordinea dată de relația de ordine prestabilită. De aici vine numele de arbore de selecție.
Reprezentarea în memorie
Ansamblul heap se reprezintă secvențial, într-un vector v, fără să fie memorate legăturile
dintre noduri. Reprezentarea este posibilă fiindcă arborele este aproape complet: dacă nodurile se
numerotează de la 1, în ordinea parcurgerii pe niveluri, de la stânga la dreapta, atunci între
indici există relațiile:
- rădăcina se află în
v[1]; - fiul stâng al nodului
v[i]estev[2*i], iar fiul drept estev[2*i+1]; - părintele nodului
v[i]estev[i/2], cu împărțire întreagă.
Numărul de noduri se reține într-o variabilă n, care este lungimea logică a vectorului. Nodul
v[i] are fiu stâng numai dacă 2*i ≤ n și fiu drept numai dacă 2*i+1 ≤ n.
Heap-ul maxim alăturat, cu 10 noduri, este memorat în vectorul:
i 1 2 3 4 5 6 7 8 9 10
v 50 30 40 25 20 35 15 10 22 18
Inserarea unei chei
Descrierea etapelor. Prin inserare, arborele trebuie să rămână ansamblu heap, deci trebuie să rămână arbore aproape complet și să păstreze relația de ordine între părinte și fii.
- Se verifică dacă mai există loc în vector, adică dacă lungimea logică
neste mai mică decât lungimea fizică. - Nodul nou își găsește locul pe ultimul nivel, în prima poziție liberă din dreapta nodurilor
existente. Când ultimul nivel s-a umplut, el deschide nivelul următor, în extremitatea stângă.
În vector, aceasta înseamnă că
ncrește cu 1, iar cheia se scrie înv[n]. - După adăugare, cheia nouă poate să nu respecte relația de ordine cu părintele ei, deci se propagă spre rădăcină: nodul nou devine nod curent, iar cheia lui se compară cu cheia părintelui.
- Cât timp nodul curent nu este rădăcina și cheia lui o depășește pe cea a părintelui, cele două chei se interschimbă, iar părintele devine nod curent.
- Propagarea se oprește când relația de ordine este respectată sau când nodul curent a ajuns rădăcina.
Exemplificarea etapelor. În ansamblul de mai sus se inserează cheia 45.
| Etapa | Ce se face | Vectorul |
|---|---|---|
| adăugarea | n devine 11, iar v[11] primește 45 | 50, 30, 40, 25, 20, 35, 15, 10, 22, 18, 45 |
| prima comparație | părintele lui v[11] este v[5] = 20; 45 > 20, deci cheile se interschimbă, iar nodul curent devine v[5] | 50, 30, 40, 25, 45, 35, 15, 10, 22, 18, 20 |
| a doua comparație | părintele lui v[5] este v[2] = 30; 45 > 30, deci cheile se interschimbă, iar nodul curent devine v[2] | 50, 45, 40, 25, 30, 35, 15, 10, 22, 18, 20 |
| a treia comparație | părintele lui v[2] este v[1] = 50; 45 < 50, deci propagarea se oprește | 50, 45, 40, 25, 30, 35, 15, 10, 22, 18, 20 |
Cheia 45 a urcat două niveluri, prin două interschimbări, iar arborele are acum 11 noduri.
Eliminarea nodului cu cheia maximă
Descrierea etapelor. Într-un heap maxim, cheia cea mai mare se află în rădăcină, deci eliminarea nodului cu cheia maximă înseamnă eliminarea rădăcinii. Și după această operație arborele trebuie să rămână aproape complet și să păstreze relația de ordine.
- Se reține cheia din rădăcină,
v[1], fiindcă ea este rezultatul operației. - Pentru ca arborele să rămână aproape complet, din el trebuie să dispară ultimul nod de pe ultimul
nivel, nu rădăcina. De aceea cheia ultimului nod se aduce în rădăcină, iar
nscade cu 1. - Cheia adusă în rădăcină poate să nu fie în relația de ordine cu fiii ei, deci se propagă spre nivelurile inferioare: rădăcina devine nod curent.
- Cât timp nodul curent are cel puțin un fiu, se alege fiul cu cheia mai mare. Dacă această cheie este mai mare decât cheia nodului curent, cele două chei se interschimbă, iar fiul devine nod curent.
- Propagarea se oprește când cheia nodului curent este mai mare sau egală cu cheile fiilor lui sau când nodul curent nu mai are fii.
Exemplificarea etapelor. Se elimină nodul cu cheia maximă din ansamblul cu 11 noduri obținut mai sus: 50, 45, 40, 25, 30, 35, 15, 10, 22, 18, 20.
| Etapa | Ce se face | Vectorul |
|---|---|---|
| extragerea | se reține v[1] = 50, cheia ultimului nod, 20, se aduce în rădăcină, iar n devine 10 | 20, 45, 40, 25, 30, 35, 15, 10, 22, 18 |
| prima comparație | fiii lui v[1] sunt v[2] = 45 și v[3] = 40; fiul cu cheia mai mare este v[2]; 20 < 45, deci cheile se interschimbă, iar nodul curent devine v[2] | 45, 20, 40, 25, 30, 35, 15, 10, 22, 18 |
| a doua comparație | fiii lui v[2] sunt v[4] = 25 și v[5] = 30; fiul cu cheia mai mare este v[5]; 20 < 30, deci cheile se interschimbă, iar nodul curent devine v[5] | 45, 30, 40, 25, 20, 35, 15, 10, 22, 18 |
| a treia comparație | v[5] are numai fiul stâng, v[10] = 18; 20 > 18, deci propagarea se oprește | 45, 30, 40, 25, 20, 35, 15, 10, 22, 18 |
Operația furnizează cheia 50, iar arborele rămas are 10 noduri și este tot heap maxim, cu cheia cea mai mare, 45, în rădăcină.
Problema propusă
Enunț. Se citesc de la tastatură un număr natural m (m ≤ 100), apoi m numere naturale, reprezentând punctajele obținute de concurenții unui concurs, și un număr natural k (k ≤ m). Se cere să se afișeze cele mai mari k punctaje, în ordine descrescătoare, separate prin câte un spațiu.
Exemplu: pentru m = 8, punctajele 12, 45, 7, 45, 30, 3, 28, 19 și k = 3 se afișează 45 45 30.
Descrierea soluției în limbaj natural. Punctajele se introduc, pe măsură ce sunt citite, într-un heap maxim, prin operația de inserare. La sfârșitul citirii, în rădăcina ansamblului se află cel mai mare punctaj.
Se aplică apoi de k ori operația de eliminare a nodului cu cheia maximă. Fiecare eliminare furnizează cel mai mare punctaj rămas și reface ansamblul, deci a doua eliminare furnizează al doilea punctaj ca mărime și așa mai departe. Punctajele astfel obținute se afișează în ordinea în care sunt extrase, care este chiar ordinea descrescătoare cerută.
Punctajele egale nu creează dificultăți, fiindcă într-un ansamblu heap pot exista chei egale: pe exemplul dat, valoarea 45 este extrasă de două ori.
Implementarea soluției.
#include <iostream>
using namespace std;
const int NMAX = 101;
int v[NMAX]; // heap-ul maxim; radacina este v[1]
int n = 0; // numarul de noduri ale ansamblului
/* Insereaza cheia x: nodul nou se adauga dupa ultimul nod, iar cheia lui se
propaga spre radacina cat timp este mai mare decat cheia parintelui. */
void inserare(int x) {
n++;
v[n] = x;
int i = n;
while (i > 1 && v[i] > v[i / 2]) {
int aux = v[i];
v[i] = v[i / 2];
v[i / 2] = aux;
i = i / 2;
}
}
/* Elimina nodul radacina si furnizeaza cheia lui: in radacina se aduce cheia
ultimului nod, care se propaga apoi spre nivelurile inferioare. */
int eliminaMaxim() {
int x = v[1];
v[1] = v[n];
n--;
int i = 1;
while (2 * i <= n) {
int j = 2 * i; // fiul stang
if (j + 1 <= n && v[j + 1] > v[j])
j++; // fiul cu cheia mai mare
if (v[i] >= v[j])
break;
int aux = v[i];
v[i] = v[j];
v[j] = aux;
i = j;
}
return x;
}
int main() {
int m, k, x;
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> x;
inserare(x);
}
cin >> k;
for (int i = 1; i <= k; i++)
cout << eliminaMaxim() << " ";
return 0;
}
Verificarea pe exemplul din enunț. După citirea celor opt punctaje, heap-ul maxim are în
rădăcină valoarea 45. Prima eliminare furnizează 45, a doua furnizează al doilea 45, iar a treia
furnizează 30. Se afișează 45 45 30.
Rezolvarea se poate citi pe educamp.ro, la adresa de mai sus.