Deo zbornika Učimo algoritme
Rečnik ili heš tabela
Rečnik, mapa, asocijativni niz ili heš tabela (eng. hash table) je struktura podataka koja čuva parove ključeva i vrednosti. Ova struktura koristi heš funkciju za indeksiranje elemenata, što omogućava brz pristup vrednostima na osnovu ključeva.
Rečnik omogućava brzo nalaženje, dodavanje i brisanje vrednosti, bez obzira na veličinu rečnika.
Kako radi rečnik?
Kada se novi par ključ-vrednost doda u rečnik, ključ prolazi kroz heš funkciju koja generiše heš kod, tj. broj koji se koristi kao indeks za pozicioniranje vrednosti u memoriji. Heš kod određuje gde će vrednost biti sačuvana u tabeli. Kada želimo da pristupimo vrednosti pomoću ključa, ključ ponovo prolazi kroz heš funkciju kako bi ukazao na mesto u tabeli gde je vrednost pohranjena.
Kako radi heš funkcija?
Heš funkcija je funkcija koja prima ulazne podatke promenljive dužine i pretvara ih u fiksnu vrednost, poznatu kao heš kod. Ovaj kod je obično broj ili niz koji reprezentuje ulazne podatke na jedinstven način, a koristi se u strukturama kao što su heš tabele, te u šifrovanju i proveri podataka.
Vremenska složenost
Pristup, umetanje i brisanje u haš tabeli obično imaju O(1) vremensku složenost u prosečnom slučaju, što znači da su izuzetno brze operacije i ne zavise od broja elemenata.
Primer: telefonski imenik
Pretpostavimo da imamo sledeće podatke za telefonski imenik koji želimo da implementiramo. Ključ za svaki unos će biti ime osobe, a vrednost broj telefona.
Ime | Broj telefona |
---|---|
Marko | 555-1234 |
Ana | 555-5678 |
Jovan | 555-8765 |
Milan | 555-4321 |
Prevođenje imena u indekse
Koristićemo prostu heš funkciju koja prevodi ime u broj, te računa njegov modulo na osnovu veličine rečnika. Rezultat ove funkcije postaje index elementa. Za početak, uzmimo da je veličina rečnika 10.
Naša heš funkcija prevodi ime u broj tako što sabira ASCII vrednosti slova. Vrednosti imena Marko su:
M
= 77a
= 97r
= 114k
= 107o
= 111
Sabiranjem vrednosti slova dobijamo sledeći broj:
77 + 97 + 114 + 107 + 111 = 506
Kada na zbir primenimo modulo 10 dobijamo sledeći indeks:
506 % 10 = 6
Istom procedurom, za ostala imena dobijamo njihove indekse:
- Ana: ASCII vrednosti su A = 65, n = 110, a = 97. Ukupno: 272. Indeks = 272 % 10 =
2
- Jovan: ASCII vrednosti su J = 74, o = 111, v = 118, a = 97, n = 110. Ukupno: 510. Indeks = 510 % 10 =
0
- Milan: ASCII vrednosti su M = 77, i = 105, l = 108, a = 97, n = 110. Ukupno: 497. Indeks = 497 % 10 =
7
Sada naš rečnik izgleda ovako:
Indeks | Podaci |
---|---|
0 | (Jovan, 555-8765) |
1 | |
2 | (Ana, 555-5678) |
3 | |
4 | |
5 | |
6 | (Marko, 555-1234) |
7 | (Milan, 555-4321) |
8 | |
9 |
Imamo rezervisano deset indeksa, od čega su četiri popunjena.
Rešavanje sudara
Zbog prirode heš funkcije, u nekom trenutku će dva različita imena dobiti isti indeks i tada dolazi do sudara. Na primer, kada u rečnik dodamo Miloša, koji ima isti indeks kao Ana.
ASCII vrednosti:
M
= 77i
= 105l
= 108o
= 111š
= 161 (u UTF-8)
Zbir slova je 562 (77 + 105 + 108 + 111 + 161), a indeks 2
(562 % 10). Tako dolazi do sudara jer se Miloš
hešira na isti indeks kao Ana
. Sudar se rešava korišćenjem lančanja, odnosno pravljenjem liste na zauzetom indeksu:
Indeks | Podaci |
---|---|
0 | (Jovan, 555-8765) |
1 | |
2 | (Ana, 555-5678) -> (Miloš, 555-6789) |
3 | |
4 | |
5 | |
6 | (Marko, 555-1234) |
7 | (Milan, 555-4321) |
8 | |
9 |
Kada imamo lančanje na nekom indeksu, vrednosti se ne pristupa u jednom koraku. Prvo, heš funkcija prevodi ime u indeks i nalazi listu. Potom traži ime u listi.
Primer u kodu
U python-u, telefonski imenik bismo napravili na sledeći način, ne razmišljajući o heširanju i sudarima:
telefonski_imenik = {
"Marko": "555-1234",
"Ana": "555-5678",
"Jovan": "555-8765",
"Milan": "555-4321",
"Miloš": "555-6789"
}
# pristup broju telefona pomoću ključa
print(telefonski_imenik["Ana"]) # izlaz: 555-5678