STL, lecția 3 – Set

Ce este și ce face setul?

Setul din C++ este un container STL alocat dinamic. Este ca un vector, doar că are 2 proprietăți importante atunci când vrem să adăugăm un element în set:

  • Elementul care urmează să fie inserat este plasat în set în așa fel încât toate elementele să fie în ordine crescătoare;
  • Dacă elementul care urmează să fie inserat există deja în set, acesta nu va mai fi inserat.

Cu alte cuvinte, setul este echivalentul mulțimii din matematică. Elementele se păstrează în ordine crescătoare și sunt unice.

Singura modalitate de a accesa elementele dintr-un set este prin iteratori.

Cum folosim în cod un set?

Pentru a folosi un set, trebuie să includem librăria <set> iar apoi să declarăm setul. Pentru a declara un set, folosim structura set<tipData> numeVar;.

Program care folosește set și funcții specifice setului. (poza 1)

În poza de mai sus am declarat un set care acceptă doar valori de tipul int. Pe liniile 9, 10 și 11 am inserat în set numerele 9, 4, respectiv -155. Din observațiile făcute la primul paragraf, rezultă că în set avem numerele stocate în această ordine: {-155, 4, 9}. Pe linia 12 încercăm să ștergem numărul 5 din set. Cum acesta nu există, nu se va șterge nimic. Pe linia 13 încercăm să inserăm numărul 4 în set. Cum acesta există deja în set, nu se va insera nimic, elementele setului rămânând {-155, 4, 9}. Pe linia 14 încercăm să ștergem elementul 4, deci în set vor rămâne elementele {-155, 9}.

Liniile 16 și 17 afișează elementele rămase în set folosind iteratori. Nu voi explica în detaliu în acest articol ce sunt iteratorii. Voi spune doar că aceștia sunt ca pozițiile elementelor dintr-un vector normal (1, 2, …, n), iar valoarea memorată de aceste poziții (v[1], v[2], …, v[n]) se găsește folosind *it.

Unde se află iteratorii begin() și end(). (poza 2)

Funcții specifice și complexități

begin()O(1)Returnează un iterator la primul element al setului (vezi poza 2)
end()O(1)Returnează un iterator la elementul inexistent din dreapta ultimului element al setului (vezi poza 2)
insert(valoare)
insert(iterator)
O(logn)Inserează un element în set
erase(valoare)O(logn)Șterge un element după valoarea lui din set
erase(iterator)O(1)Șterge un element după un iterator din set
clear()O(n)Șterge toate elementele din set
empty()O(1)Returnează true dacă setul nu conține elemente, altfel returnează false
size()O(1)Returnează numărul de elemente din set

Informații suplimentare

  • Oriunde am declara setul, acesta este inițial vid;
  • Elementele acestui container sunt alocate dinamic. Ce înseamnă acest lucru? Să comparăm un vector(array) cu un set.
    La declararea unui vector (int v[100]), se alocă automat 100 de spații din memorie pentru a putea fi folosite de acest vector. Este important de menționat că aceste spații de memorie sunt întotdeauna puse una după alta. Atunci când declarăm un set, se alocă spațiu pentru unele funcții precum size().
    Atunci când inserăm un element în set, se alocă spațiu dinamic pentru acest element. Acesta poate fi plasat oriunde în memorie. Dar dacă memoria este aleasă aleatoriu pentru elemente, cum păstrăm ordinea elementelor? Aici intervin în discuție iteratorii. Fiecare element din set are câte un iterator care păstrează informații precum: adresa de memorie a elementului precedent, adresa de memorie a elementului următor și valoarea acestui element.
Program folosit pentru a afla locațiile unde se memorează elementele unui set. (poza 3)
Output-ul programului. Puteți observa cum adresele de memorie nu sunt alocate una după alta. (poza 4)
  • Folosind observațiile făcute la punctul anterior și ideea că fiecare element este adăugat în set în așa fel încât acesta să rămână ordonat, putem deduce faptul că este imposibil să accesăm, de exemplu, al treilea element, printr-o sintaxă de genul v[3] (v[2]), ci doar cu iteratori.
  • Putem specifica o funcție de comparare între elemente, astfel încât un element să fie inserat după un nou criteriu de ordonare.
    Această funcție este specificată la declararea setului. Există deja 2 funcții predefinite, acestea sunt less<tipData>() și greater<tipData>().
Folosirea funcțiilor de comparare definite și predefinite. (poza 5)
  • Este posibil să știți de la vectori(arrays) că aceștia se transmit automat prin adresă ca parametrii unor funcții, lucru care împiedică copierea tuturor elementelor și utilizarea memoriei (și timpului) degeaba. Acest lucru nu se întâmplă și cu containerele STL. Transmiterea unui set, de exemplu, ca parametru într-o funcție va determina copierea acestuia în întregime. Încercați pe cât posibil să evitați acest lucru folosind transmiterea prin adresă, cum am făcut și în poza a treia.

Concluzie

Sper că acest articol v-a fost de ajutor. Dacă sesizați greșeli de orice fel în acest articol sau aveți întrebări, scrieți în secțiunea de comentarii.

Mult succes!