Hash táblázat

Az oldal jelenlegi verzióját még nem ellenőrizték tapasztalt közreműködők, és jelentősen eltérhet a 2019. október 12-én felülvizsgált verziótól ; az ellenőrzések 9 szerkesztést igényelnek .
Hash táblázat
Típusú asszociatív tömb
Feltalálás éve 1953
Bonyolultság az O-szimbólumokban
Átlagos Legrosszabb esetben
Memória fogyasztás Tovább) Tovább)
Keresés O(1) Tovább)
Beszúrás O(1) Tovább)
Eltávolítás O(1) Tovább)
 Médiafájlok a Wikimedia Commons oldalon

A hash tábla  egy olyan adatstruktúra , amely megvalósítja az asszociatív tömb interfészt , azaz lehetővé teszi párok (kulcs, érték) tárolását és három művelet végrehajtását: egy új pár hozzáadásának műveletét, a keresési műveletet és a törlési műveletet. párosítás kulcsonként.

Bevezetés

A hash tábláknak két fő változata van: láncolt és nyílt címzés. A hash tábla tartalmaz néhány tömböt , amelynek elemei párok (nyitott cím hash tábla) vagy párok listája (láncolt hash tábla).

A művelet végrehajtása egy hash táblában a kulcs hash függvényének kiszámításával kezdődik. Az eredményül kapott hash érték indexként működik a tömbben . Ezután a végrehajtott művelet (hozzáadás, törlés vagy keresés) átirányításra kerül az objektumra, amely a tömb megfelelő cellájában van tárolva .

Ütközésnek nevezzük azt a helyzetet, amikor a különböző kulcsokhoz ugyanazt a hash értéket kapjuk . Az ilyen események nem olyan ritkák - például ha csak 23 elemet illesztünk be egy 365 cellás hash táblába, az ütközés valószínűsége már meghaladja az 50%-ot (ha minden elem azonos valószínűséggel bármelyik cellába eshet) - lásd a születésnapot paradoxon . Ezért az ütközés-feloldó mechanizmus minden hash-tábla fontos része.

Egyes speciális esetekben teljesen elkerülhető az ütközés. Például, ha az elemek összes kulcsa előre ismert (vagy nagyon ritkán változik), akkor számukra találhat valami tökéletes hash függvényt , amely ütközések nélkül elosztja azokat a hash tábla cellái között. Az ezeket a hash-függvényeket használó hash-táblázatoknak nincs szükségük ütközés-feloldó mechanizmusra, és ezeket közvetlen címkivonat-táblázatoknak nevezik .

A tárolt elemek számát osztva a tömb méretével (a lehetséges hash értékek számával) hash tábla terhelési tényezőnek nevezzük, és ez egy fontos paraméter, amely meghatározza a műveletek átlagos végrehajtási idejét.

Hash tábla tulajdonságai

A hash táblák fontos tulajdonsága, hogy bizonyos ésszerű feltételezések mellett mindhárom művelet (keresés, beillesztés, elemek törlése) átlagosan időben befejeződik . Ez azonban nem garantálja, hogy egyetlen művelet végrehajtási ideje kicsi. Ez annak a ténynek köszönhető, hogy a kitöltési tényező egy bizonyos értékének elérésekor újra kell építeni a hash tábla indexét: növelni kell a tömb méretét , és minden párt újra hozzáadni az üres hash táblához.

Ütközésfelbontás

Az ütközések megoldásának többféle módja van .

Láncmódszer

A H tömb minden cellája egy mutató az azonos kulcskivonat-értéknek megfelelő kulcs-érték párok összekapcsolt listájára (láncára). Az ütközések egyszerűen egy elemnél hosszabb láncokat eredményeznek.

Egy elem megkereséséhez vagy törléséhez meg kell keresni a megfelelő lánc összes elemét, hogy megtaláljuk az adott kulcsú elemet. Egy elem hozzáadásához hozzá kell adni egy elemet a megfelelő lista végéhez vagy elejéhez, és ha a kitöltési tényező túl nagy lesz, növelje meg a H tömb méretét, és építse újra a táblázatot.

Feltételezve, hogy minden elem azonos valószínűséggel eshet a H táblázat tetszőleges pozíciójába, függetlenül attól, hogy hova került a másik elem, az elemkeresési művelet átlagos ideje Θ (1 + α ), ahol α  a táblázat kitöltési tényezője.

Címzés megnyitása

A H tömb magát a kulcs-érték párokat tárolja. Az elembeillesztési algoritmus a H tömb celláit bizonyos sorrendben ellenőrzi, amíg meg nem találjuk az első szabad cellát, amelybe az új elemet írjuk. Ezt a sorrendet a rendszer menet közben számítja ki, így memóriát takarít meg a láncolt hash táblákban szükséges mutatók számára.

Azt a szekvenciát , amelyben a kivonattábla celláit kikeresi, próbasorozatnak nevezzük . Általános esetben csak az elemkulcstól függ, vagyis egy h 0 ( x ), h 1 ( x ), ..., h n  - 1 ( x ) sorozatról van szó, ahol x  az elemkulcs , és h i ( x ) - tetszőleges függvények, amelyek az egyes kulcsokat a hash tábla egy cellájára képezik le. A sorozat első eleme általában megegyezik a kulcsból származó hash függvény értékével, a többit pedig ebből számítják ki a következő módok egyikével. A keresési algoritmusok sikeres működéséhez a próbák sorrendjének olyannak kell lennie, hogy a hash tábla minden celláját pontosan egyszer vizsgálják meg.

A keresési algoritmus a hash tábla celláiban a beszúrással megegyező sorrendben keres, amíg vagy a kívánt kulcsú elemet vagy egy szabad cellát (ami azt jelenti, hogy nincs elem a hash táblában) nem talál.

Az elemek eltávolítása egy ilyen rendszerben kissé nehézkes. Általában ezt teszik: minden cellához beállítanak egy logikai jelzőt, jelezve, hogy egy elemet töröltek-e vagy sem. Ekkor egy elem eltávolítása abból áll, hogy beállítjuk ezt a jelzőt a hash tábla megfelelő cellájára, ugyanakkor módosítani kell egy meglévő elem keresési eljárását úgy, hogy az a törölt cellákat foglaltnak tekintse, és az eljárást hozzáadásához, így szabadnak tekinti őket, és visszaállítja a jelzőértéket, amikor hozzáadják őket.

Mintasorozatok

Az alábbiakban bemutatunk néhány gyakori mintasorozattípust. Rögtön megadjuk, hogy a hash tábla mintasorozatának és celláinak elemeinek számozása nullától legyen, N  pedig a hash tábla mérete (és ahogy fentebb megjegyeztük, a minták sorozatának hossza is).


Az alábbiakban látható a kettős kivonatolást bemutató kód:

Megvalósítás C-ben // A DICT_CELL_COUNT prímszámnak kell lennie! #define DICT_CELL_COUNT 30011 char * szWordAr [ DICT_CELL_COUNT ]; unsigned int uWordArSize = 0 ; #define WORD_IDX_BAD (( unsigned int )-1) unsigned int uWordIdxByHashAr [ DICT_CELL_COUNT ]; // inicializálni kell az elemeket a WORD_IDX_BAD értékkel #define STRIDE_1 17 #define STRIDE_2 13 // A GetOrAddWordIdx( .. ) függvény a pcszWord szó indexét adja vissza az szWordAr tömbben. // Ez hozzáadja a pcszWord szót a szWordAr szótárhoz, ha nincs ott. unsigned int GetOrAddWordIdx ( const char * const pcszWord ) { előjel nélküli int uHash1 = 0 , uHash2 = 0 ; const unsigned char * cbtWordCharCur = ( const unsigned char * ) pcszWord ; // A pcszWord szó két kivonatának kiszámítása: // uHash1 a [ 0 .. DICT_CELL_COUNT - 1 ] tartományban található // uHash2 a [ 1 .. DICT_CELL_COUNT - 1 ] tartományban található csináld { uHash1 *= STRIDE_1 ; uHash1 += ( STRIDE_2 * * cbtWordCharCur ); uHash2 *= STRIDE_2 ; uHash2 += ( STRIDE_1 * * cbtWordCharCur ); } while ( * ( cbtWordCharCur ++ ) ); // Megjegyzés: növekmény! // #1: a cbtWordCharCur az utolsó karakterre mutat. '\0' a pcszWord-ben, a // a #2-ben lesz használatos uHash1 %= DICT_CELL_COUNT ; uHash2 %= ( DICT_CELL_COUNT - 1 ); ++ uHash2 ; while ( uWordIdxByHashAr [ uHash1 ] != WORD_IDX_BAD ) { if ( ! strcmp ( pcszWord , szWordAr [ uWordIdxByHashAr [ uHash1 ] ) ) _ return uWordIdxByHashAr [ uHash1 ]; uHash1 += uHash2 ; uHash1 %= DICT_CELL_COUNT ; } strcpy ( szWordAr [ uWordIdxByHashAr [ uHash1 ] = // Megjegyzés: hozzárendelés! uWordArSize ] = // Megjegyzés: hozzárendelés! ( char * ) malloc ( // a pcszWord hossza plusz 1: ( const char * ) cbtWordCharCur - // #2: cbtWordCharCur értéke #1 -ből pcszWord ), pcszWord ); return uWordArSize ++ ; // Megjegyzés: növekmény! } // unsigned int GetOrAddWordIdx( const char* const pcszWord )

Lásd még

Irodalom

  • Kormen, T. , Leizerson, C. , Rivest, R. , Stein, K. 11. fejezet. Hash-táblázatok. // Algoritmusok: konstrukció és elemzés = Introduction to Algorithms / Szerk. I. V. Krasikova. - 2. kiadás - M. : Williams, 2005. - 1296 p. — ISBN 5-8459-0857-4 .