HashMap
Príklad implementácie štruktúry hash mapy. Táto implementácia ukladá len hodnoty typu int
.
Jednotlivé hodnoty sú uložené do “vedier” (z angl. bucket), alebo chlievikov, pre hodnoty, ktorým je vypočítaný rovnaký
hash.
Prístup cez vypočítaný hash zrýchli prístup na O(1)+O(n) kde n je dĺžka najväčšieho bucketu. Čo je lepšie ako
klasický zoznam.
#include <iostream>
#include <vector>
using namespace std;
class PrvokZoznamu {
public:
int hodnota;
PrvokZoznamu *dalsi = nullptr;
PrvokZoznamu *predchadzajuci = nullptr;
/**
* @brief Konštruktor pre vytvorenie nového prvku so zadanou hodnotou.
* @param hodnota Hodnota, ktorú prvok bude obsahovať.
*/
explicit PrvokZoznamu(int hodnota) : hodnota(hodnota) {
}
/**
* @brief Vytlačí hodnoty všetkých prvkov v zozname, počnúc týmto prvkom.
*/
void print() {
cout << hodnota << ", ";
if (dalsi) {
dalsi->print();
}
}
/**
* @brief Vyhľadá prvok so zadanou hodnotou v zozname.
* @param najdi Hľadaná hodnota.
* @return Ukazateľ na prvý nájdený prvok alebo nullptr, ak sa nenájde.
*/
PrvokZoznamu *find(int najdi) {
if (hodnota == najdi) return this;
if (dalsi) return dalsi->find(najdi);
return nullptr;
}
/**
* @brief Získa posledný prvok v zozname.
* @return Ukazateľ na posledný prvok.
*/
PrvokZoznamu *last() {
if (!dalsi) return this;
else return dalsi->last();
}
/**
* @brief Získa prvý prvok v zozname.
* @return Ukazateľ na prvý prvok.
*/
PrvokZoznamu *first() {
if (!predchadzajuci) return this;
else return predchadzajuci->first();
}
/**
* @brief Vloží nový prvok so zadanou hodnotou na koniec zoznamu, ak už neexistuje.
* @param nova_hodnota Hodnota nového prvku.
* @return Ukazateľ na novo vložený prvok alebo nullptr, ak už existuje.
*/
PrvokZoznamu *insert(int nova_hodnota) {
PrvokZoznamu *obsahuje_hodnotu = find(nova_hodnota);
if (obsahuje_hodnotu) return nullptr;
PrvokZoznamu *posledny = last();
PrvokZoznamu *vkladany = new PrvokZoznamu(nova_hodnota);
posledny->dalsi = vkladany;
vkladany->predchadzajuci = posledny;
return vkladany;
}
/**
* @brief Odstráni prvok so zadanou hodnotou zo zoznamu.
* @param na_vymazanie Hodnota prvku, ktorý sa má odstrániť.
* @return Ukazateľ na nový začiatok zoznamu po odstránení.
*/
PrvokZoznamu *remove(int na_vymazanie) {
if (na_vymazanie == hodnota) {
if (predchadzajuci) {
predchadzajuci->dalsi = this->dalsi;
}
if (dalsi) {
dalsi->predchadzajuci = predchadzajuci;
}
PrvokZoznamu *prvy = first();
if (prvy == this) {
prvy = prvy->dalsi;
delete this;
}
return prvy;
}
if (dalsi) dalsi->remove(na_vymazanie);
}
/**
* @brief Vymaže celý zoznam, počnúc posledným prvkom a idúc späť k prvému.
*/
void clear() {
PrvokZoznamu *posledny = last();
while (posledny) {
if (!posledny->predchadzajuci) {
delete posledny;
break;
}
posledny = posledny->predchadzajuci;
delete posledny->dalsi;
}
}
};
class HashMap {
public:
vector<PrvokZoznamu *> buckets;
int kapacita;
/**
* @brief Konštruktor pre vytvorenie hash mapy so zadanou kapacitou.
* @param kapacita Počet "vedier" hash mapy.
*/
explicit HashMap(int kapacita) : kapacita(kapacita) {
for (int i = 0; i < kapacita; i++) {
buckets.push_back(nullptr);
}
}
/**
* @brief Vypočíta hash pre danú hodnotu.
* @param hodnota Hodnota, pre ktorú sa má vypočítať hash.
* @return Výsledný hash ako index do poľa.
*/
int calc_hash(int hodnota) const {
return hodnota % kapacita;
}
/**
* @brief Vytlačí obsah celej hash mapy.
*/
void print() {
for (int i = 0; i < buckets.size(); i++) {
cout << "[" << i << "]" << " : ";
if (buckets[i]) buckets[i]->print();
else cout << "NULL";
cout << endl;
}
}
/**
* @brief Vloží hodnotu do hash mapy, ak ešte neexistuje.
* @param hodnota Hodnota, ktorá sa má vložiť.
* @return true, ak bola hodnota vložená; false, ak už existovala.
*/
bool insert(int hodnota) {
const int hash = calc_hash(hodnota);
if (!buckets[hash]) {
buckets[hash] = new PrvokZoznamu(hodnota);
return true;
} else {
PrvokZoznamu *zoznam = buckets[hash];
PrvokZoznamu *vlozeny = zoznam->insert(hodnota);
return vlozeny != nullptr;
}
}
/**
* @brief Skontroluje, či daná hodnota existuje v hash mape.
* @param hodnota Hľadaná hodnota.
* @return Index vedra, v ktorom sa hodnota nachádza; -1 ak neexistuje.
*/
int contains(int hodnota) {
const int hash = calc_hash(hodnota);
if (!buckets[hash]) return -1;
PrvokZoznamu *najdeny = buckets[hash]->find(hodnota);
return najdeny != nullptr ? hash : -1;
}
/**
* @brief Odstráni danú hodnotu z hash mapy.
* @param hodnota Hodnota, ktorá sa má odstrániť.
*/
void remove(int hodnota) {
const int hash = calc_hash(hodnota);
if (!buckets[hash]) return;
PrvokZoznamu *novy_prvy = buckets[hash]->remove(hodnota);
buckets[hash] = novy_prvy;
}
/**
* @brief Dekštruktor, ktorý uvoľní všetky prvky hash mapy.
*/
~HashMap() {
for (PrvokZoznamu *bucket: buckets) {
bucket->clear();
}
}
};
Vysvetlenie
Trieda PrvokZoznamu
Predstavuje prvok obojstranného zoznamu (doubly linked list
), ktorý sa používa v každom “vedre” hash mapy.
Atribúty:
int hodnota
– hodnota, ktorú prvok reprezentujePrvokZoznamu *dalsi
– ukazateľ na nasledujúci prvokPrvokZoznamu *predchadzajuci
– ukazateľ na predchádzajúci prvok
Metódy:
explicit PrvokZoznamu(int hodnota)
- Konštruktor, ktorý nastaví hodnotu prvku.
void print()
- Rekurzívne vypisuje všetky hodnoty od tohto prvku ďalej.
PrvokZoznamu* find(int najdi)
- Hľadá v zozname prvok s danou hodnotou.
- Ak nájde, vráti pointer, inak
nullptr
.
PrvokZoznamu* last()
- Vráti posledný prvok v zozname (prechádza cez
dalsi
).
PrvokZoznamu* first()
- Vráti prvý prvok v zozname (prechádza späť cez
predchadzajuci
).
PrvokZoznamu* insert(int nova_hodnota)
- Najprv overí, či hodnota už neexistuje (
find()
). - Ak nie, vytvorí nový prvok a vloží ho na koniec zoznamu.
- Vráti pointer na nový prvok alebo
nullptr
, ak už existuje.
PrvokZoznamu* remove(int na_vymazanie)
- Odstráni prvok so zadanou hodnotou.
- Upraví odkazy
predchadzajuci
adalsi
, aby bol zoznam stále súvislý. - Ak sa odstraňuje prvý prvok, vráti nový začiatok zoznamu.
- Nevracia nič pri zlyhaní, čo môže byť nebezpečné (viď nižšie).
void clear()
- Vymaže celý zoznam od konca smerom k začiatku, bezpečne uvoľní pamäť.
Trieda HashMap
Trieda, ktorá simuluje jednoduchú hash mapu.
Atribúty:
vector<PrvokZoznamu*> buckets
– pole “vedier”, v ktorých sú ukazatele na zoznamy (kvôli kolíziám).int kapacita
– počet vedier, ktoré určujú, ako dobre hash rozdeľuje dáta.
Metódy:
explicit HashMap(int kapacita)
- Vytvorí hash mapu s daným počtom prázdnych vedier (inicializovaných na
nullptr
).
int calc_hash(int hodnota)
- Jednoduchá hashovacia funkcia:
hodnota % kapacita
- Vracia index vedra, do ktorého patrí hodnota.
void print()
- Vypíše všetky vedrá a ich obsahy (volá
print()
naPrvokZoznamu
).
bool insert(int hodnota)
- Vloží novú hodnotu do správneho vedra (podľa hash).
- Ak vedro ešte neexistuje, vytvorí nový prvok.
- Ak existuje, volá
insert()
na zozname. - Vráti
true
ak sa hodnota vložila, inakfalse
.
int contains(int hodnota)
- Skontroluje, či hodnota existuje v danom vedre (pomocou
find()
). - Ak sa nájde, vráti index vedra, inak
-1
.
void remove(int hodnota)
- Odstráni prvok s danou hodnotou.
- Ak po odstránení je prvý prvok iný, aktualizuje vedro.
~HashMap()
- Destruktor, ktorý volá
clear()
pre každý zoznam vo vedrách a uvoľní pamäť.