Hash tabulka

Aktuální verze stránky ještě nebyla zkontrolována zkušenými přispěvateli a může se výrazně lišit od verze recenzované 12. října 2019; kontroly vyžadují 9 úprav .
Hash tabulka
Typ asociativní pole
Rok vynálezu 1953
Složitost v O-symbolech
Průměrný Při nejhorším
Spotřeba paměti Na) Na)
Vyhledávání O(1) Na)
Vložit O(1) Na)
Odstranění O(1) Na)
 Mediální soubory na Wikimedia Commons

Hašovací tabulka  je datová struktura , která implementuje rozhraní asociativního pole , konkrétně vám umožňuje ukládat páry (klíč, hodnota) a provádět tři operace: operaci přidání nového páru, operaci vyhledávání a operaci odstranění spárovat pomocí klíče.

Úvod

Existují dvě hlavní varianty hashovacích tabulek: řetězené a otevřené adresování. Hashovací tabulka obsahuje nějaké pole , jehož prvky jsou dvojice (otevřená tabulka hash adres) nebo seznamy dvojic (zřetězená tabulka hash).

Provedení operace v hašovací tabulce začíná výpočtem hašovací funkce klíče. Výsledná hodnota hash funguje jako index do pole . Poté je prováděná operace (přidat, odstranit nebo najít) přesměrována na objekt, který je uložen v odpovídající buňce pole .

Situace, kdy je získána stejná hodnota hash pro různé klíče, se nazývá kolize . Takové události nejsou tak vzácné - např. při vložení pouhých 23 prvků do hashovací tabulky 365 buněk už pravděpodobnost kolize přesáhne 50% (pokud každý prvek může spadnout do libovolné buňky se stejnou pravděpodobností) - viz narozeniny paradox . Proto je mechanismus řešení kolizí důležitou součástí každé hashovací tabulky.

V některých speciálních případech je možné kolizím zcela zabránit. Pokud jsou například všechny klíče prvků předem známé (nebo se mění velmi zřídka), pak pro ně lze najít nějakou perfektní hashovací funkci , která je bez kolizí rozdělí mezi buňky hashovací tabulky. Hashovací tabulky, které používají tyto hashovací funkce, nepotřebují mechanismus řešení kolizí a nazývají se hashovací tabulky přímých adres .

Počet uložených prvků dělený velikostí pole (počet možných hodnot hash) se nazývá faktor zatížení hashovací tabulky a je důležitým parametrem, který určuje průměrnou dobu provádění operací.

Vlastnosti hashovací tabulky

Důležitou vlastností hashovacích tabulek je, že za určitých rozumných předpokladů jsou všechny tři operace (vyhledávání, vkládání, mazání prvků) dokončeny v průměru za čas . To však nezaručuje, že doba provedení jedné operace je krátká. To je způsobeno skutečností, že při dosažení určité hodnoty faktoru plnění je nutné znovu sestavit index hashovací tabulky: zvětšit hodnotu velikosti pole a znovu přidat všechny páry do prázdné hashovací tabulky.

Rozlišení kolize

Existuje několik způsobů, jak vyřešit kolize .

Řetězová metoda

Každá buňka pole H je ukazatelem na propojený seznam (řetězec) párů klíč–hodnota odpovídající stejné hodnotě hash klíče. Výsledkem kolizí jsou jednoduše řetězce, které jsou delší než jeden prvek.

Nalezení nebo smazání prvku vyžaduje prohledání všech prvků odpovídajícího řetězce, aby se našel prvek s daným klíčem. Chcete-li přidat prvek, musíte přidat prvek na konec nebo začátek odpovídajícího seznamu, a pokud se faktor vyplnění příliš zvětší, zvětšete velikost pole H a znovu sestavte tabulku.

Za předpokladu, že každý prvek může spadnout na jakoukoli pozici tabulky H se stejnou pravděpodobností a bez ohledu na to, kde skončil jakýkoli jiný prvek, je průměrná doba operace vyhledávání prvku Θ (1 + α ), kde α  je faktor vyplnění tabulky.

Otevřít adresování

Pole H ukládá samotné páry klíč–hodnota. Algoritmus vkládání prvků kontroluje buňky pole H v určitém pořadí, dokud není nalezena první volná buňka, do které bude nový prvek zapsán. Toto řazení je vypočítáváno za běhu, čímž se šetří paměť pro ukazatele požadované v řetězených hašovacích tabulkách.

Sekvence, ve které se vyhledávají buňky hashovací tabulky, se nazývá sekvence sondy . V obecném případě záleží pouze na klíči prvku, to znamená, že se jedná o posloupnost h 0 ( x ), h 1 ( x ), ..., h n  - 1 ( x ), kde x  je klíč prvku a h i ( x ) - libovolné funkce, které mapují každý klíč na buňku v tabulce hash. První prvek v sekvenci se zpravidla rovná hodnotě nějaké hashovací funkce z klíče a zbytek se z ní vypočítá jedním z následujících způsobů. Aby vyhledávací algoritmy fungovaly úspěšně, sekvence sond musí být taková, aby byly všechny buňky hashovací tabulky naskenovány právě jednou.

Algoritmus hledání prohledává buňky hašovací tabulky ve stejném pořadí jako při vkládání, dokud nenajde buď prvek s požadovaným klíčem, nebo volnou buňku (což znamená, že v hašovací tabulce není žádný prvek).

Odstranění prvků v takovém schématu je poněkud obtížné. Obvykle to dělají: pro každou buňku nastaví booleovský příznak, který označí, zda byl prvek v ní odstraněn nebo ne. Pak odstranění prvku spočívá v nastavení tohoto příznaku pro příslušnou buňku hashovací tabulky, ale zároveň je nutné upravit postup hledání existujícího prvku tak, aby smazané buňky považoval za obsazené a postup za jejich přidání, aby je považoval za volné a po přidání resetuje hodnotu příznaku.

Ukázkové sekvence

Následují některé běžné typy ukázkových sekvencí. Ihned upřesněme, že číslování prvků sekvence vzorků a buněk hashovací tabulky je od nuly a N  je velikost hashovací tabulky (a jak bylo uvedeno výše, také délka sekvence vzorků).


Níže je kód demonstrující dvojité hashování:

Implementace v C // DICT_CELL_COUNT musí být prvočíslo! #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 ]; // musíte inicializovat prvky s hodnotou WORD_IDX_BAD #define STRIDE_1 17 #define STRIDE_2 13 // Funkce GetOrAddWordIdx( .. ) vrací index slova pcszWord v poli szWordAr. // Toto přidá slovo pcszWord do slovníku szWordAr, pokud tam není. unsigned int GetOrAddWordIdx ( const char * const pcszWord ) { unsigned int uHash1 = 0 , uHash2 = 0 ; const unsigned char * cbtWordCharCur = ( const unsigned char * ) pcszWord ; // Vypočítejte dva hashe slova pcszWord: // uHash1 leží v rozsahu [ 0 .. DICT_CELL_COUNT - 1 ] // uHash2 leží v rozsahu [ 1 .. DICT_CELL_COUNT - 1 ] dělat { uHash1 *= STRIDE_1 ; uHash1 += ( STRIDE_2 * * cbtWordCharCur ); uHash2 *= STRIDE_2 ; uHash2 += ( STRIDE_1 * * cbtWordCharCur ); } while ( * ( cbtWordCharCur ++ ) ); // Poznámka: zvýšit! // #1: cbtWordCharCur ukazuje na poslední znak. '\0' v pcszWord, // bude použito v #2 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 ] = // Poznámka: přiřazení! uWordArSize ] = // Pozn.: zadání! ( char * ) malloc ( // délka pcszWord plus 1: ( const char * ) cbtWordCharCur - // #2: použijte hodnotu cbtWordCharCur z #1 pcszWord ), pcszWord ); return uWordArSize ++ ; // Poznámka: zvýšit! } // unsigned int GetOrAddWordIdx( const char* const pcszWord )

Viz také

Literatura

  • Kormen, T. , Leizerson, C. , Rivest, R. , Stein, K. Kapitola 11. Hash tabulky. // Algoritmy: konstrukce a analýza = Introduction to Algorithms / Ed. I. V. Krasíková. - 2. vyd. - M. : Williams, 2005. - 1296 s. — ISBN 5-8459-0857-4 .