Tablice z haszowaniem.

Przypuśćmy, że potrzebujemy struktury danych przechowującej pary: pozwalającej wykonywać następujące operacje: w jak najkrótszym czasie ...

Uwaga 1: powyżej założyliśmy że klucz jest napisem a wartość jest liczbą całkowita; ta konwencja obowiązuje we wszystkich zadaniach z tej lekcji.

Zauważmy że jeśli zbiór wszystkich kluczy nie jest zbyt duży, to prawdopodobnie możemy każdy klucz utożsamić z pewną liczbą całkowitą, i użyć tej liczby jako indeksu do tablicy wskazującego element zawierający wartość odpowiadająca kluczowi. Co jednak zrobić jeśli zbiór wszystkich kluczy jest "gigantyczny" ? Nie możemy przecież używać "gigantycznej tablicy" !!!

Wtedy problem można rozwiązać przy pomocy tzw funkcji haszującej:

która każdemu kluczowi przyporządkowuje liczbę całkowitą ze zbioru {0, ..., m-1}, przy czym "m" jest dużo mniejsze niż liczba wszystkich kluczy.  Niech "k" będzie kluczem.  Wartość "h(k)" służy nam jako indeks do tablicy T, której element (czyli T[h(k)]) identyfikuje listę elementów z różnymi kluczami ale o tej samej wartości funkcji haszującej.  Sens jest następujący: "lepiej mieć kilka krótkich list elementów niż jedną długą listę".  Zauważmy że tablice z funkcją haszującą to uogólnienie "zwykłych tablic" jak i "zwykłych list".
 

Dobra funkcja haszująca to taka, że przeciwobrazy elementów ze zbioru {0, ..., m-1} są "w miarę" równej mocy.

Uwaga 2: w poniższych zadaniach często traktujemy "klucz" jako dużą liczbę całkowitą: litery klucza (duże i małe) należy traktować jako "cyfry" z zakresu [0 .. 2*26-1], jest to więc liczba zapisana przy podstawie 2*26=52.
 

Zadanie 32


Zaimplementuj tablice z haszowaniem, czyli funkcje: używając następujących funkcji haszujących:
  1. haszowanie przez mnożenie + suma kodów

  2. kluczowi przyporządkowujemy liczbę (int)floor( m * modf(s*A, &x) )
    gdzie s = suma kodów liter klucza
    oraz gdzie A = (sqrt(5)-1)/2
    floor() - zwraca podłogę;  modf() - zwraca część ułamkową
    .
  3. haszowanie modularne + komb. liniowa kodów i liczb pierwszych

  4. kluczowi przyporządkowujemy liczbę s % m (czyli "s mod m")
    gdzie "m" jest liczbą pierwszą
    oraz gdzie  s = k_1*a_1 + ... + k_q*a_q  przy czym "k_i" to kody liter klucza
    natomiast "a_i" to różne liczby pierwsze (ustalone pierwotnie)
    .
  5. haszowanie uniwersalne

  6. podobne do poprzedniego, z tym że :
    "m" jest liczbą pierwszą >=2*26-1
    "a_i" to wybrane losowo elementy zbioru {0, ..., m-1} (wybrane na początku działania programu)
    .
  7. "prawdziwe"  haszowanie modularne

  8. klucz "k" traktujemy jako dużą liczbę całkowitą (patrz Uwaga 2);
    kluczowi przyporządkowujemy k % m

    Wskazówka: jak obliczyć wartość "k % m" jeśli "k" jest bardzo dużą liczbą całkowitą ?
    niech "k_i" oznacza kolejne cyfry klucza (i=0, ..., q-1), zapisanego przy podstawie "b", przy czym "k_0" jest najbardzej znaczącą cyfrą; możemy obliczyć wartość liczbową klucza następująco:
            s:=k_0;
            for i:=1 to q-1 do s := s*b + k_i
            // "s" zawiera wartość liczbową "k"
    okazuje się że wartość "k % m" można obliczyć analogicznie (oczywiscie wymaga to dowodu !?!):
            s:=k_0;
            for i:=1 to q-1 do s := ( s*b + k_i ) mod m
            // "s" zawiera wartość liczbową "k % m"
            // unikamy dużych wartości "s" !!!
    .

użyj danych z plików "const.txt" i "modem.txt"; pliki te zawierają słowa o długości odpowiednio <=16 i <=22; w każdym pliku słowa są różne (jednak rozróżnia się duże i małe litery)

każde słowo traktujemy jako "klucz", natomiast jego nr porządkowy jako "wartość"

dla danego "pliku ze słowami" należy wypróbować wszystkie funkcje haszujące wymienione wyżej

dla każdej funkcji haszującej należy wykonać następujące czynności:

  1. sprawdzić czy nasza tablica z haszowaniem działa poprawnie na dostarczonych danych (czy dla danego klucza zwraca odpowiednią wartość)
  2. obliczyć maksymalną długość listy elementów, średnią długość oraz odchylenie standardowe