Algorytmy i złożoności Wykład 5. Haszowanie (hashowanie, mieszanie)

Podobne dokumenty
Haszowanie. dr inż. Urszula Gałązka

Tablice z haszowaniem

Tablice z haszowaniem

Algorytmy i struktury danych. Wykład 6 Tablice rozproszone cz. 2

struktury danych dla operacji słownikowych

1 Podstawy c++ w pigułce.

Techniki multimedialne

Systemy liczenia. 333= 3*100+3*10+3*1

Wykład 4. Tablice z haszowaniem

Techniki wyszukiwania danych haszowanie

Zaawansowane algorytmy i struktury danych

Pracownia Komputerowa wykład V

1 Podstawy c++ w pigułce.

Tablice. Monika Wrzosek (IM UG) Podstawy Programowania 96 / 119

ć ć Ą Ź Ż Ą Ż ć Ą Ż Ź

Ą ź Ą Ą Ś Ó Ą

Ł Ż Ń Ń ć

Ś ź Ś Ś

Ż Ń Ś Ł Ó Ś ń Ż ń ć Ż ć ń ź Ż ć ć ć ń ń ć Ż Ż ć

Ę Ł Ź Ł

Ł Ż

Ł Ę Ż Ą Ęć Ń Ń Ł Ę

Ł ć Ś ć Ś ć ć Ę ź ć ć

ć

Ą Ś Ó

Ż Ż

ń ć Ł Ą

Ł ć Ł ć ć ć ć Ń ć ć

Ł Ś Ś Ó ń

Ó Ó Ę ź

Ę ż Ó Ł Ść ą ą ą Ą ć ż ą ż ń ą ć ż ć Ę ą ż ą ą ż ą ź ą ń ą ń ą ą ż ć

ś ś Ż ś Ń Ń Ę Ł ć ś Ł


Algorytmy i struktury danych. Wykład 4 Tablice nieporządkowane i uporządkowane

ć ę ę ć ę Ś ę Ń ę ź ę ę ę Ś ę ę ę Ó Ł Ł Ę Ą ę

Ó Ą ź ć Ę Ń Ę


Ł Ł Ę Ż ź

ź ć

Ł Ś Ę Ł Ś Ś Ś Ą ń ń Ó

ć ć Ę Ó Ś ż ż Ś ż ż ż Ęć ż ć ć ż ż

ż ó ś Ą ć ó ó ó ś ś ś ó ś Ł ś

Ą Ó Ź Ą Ź Ź

ń ż ń ń Ą ń ż ż ń ż ż ż Ż ń Ą ń

ź Ł Ą Ż Ń Ń Ś Ń ć

ż ż Ę Ę Ą Ó

Ż Ą ź ź ź ź

ż ć Ń Ł Ż Ść Ść ć Ż Ść Ż ć ć Ż ź Ś ć ć Ó ć ć Ść

ć ć

ć ż Ż Ż Ą Ż Ż Ż

Ż Ż Ł

ż Ś ż ż ć ć Ś Ź Ą

Ę Ł ź Ś ź ź ź

ź Ż Ż Ś ć ć Ł ż Ż Ż Ż Ż Ł Ż Ł Ż Ż Ż ż ż ż ż ż ż Ż ć Ż Ś Ś Ń Ść

ć ć Ść ć Ść ć ć ć ć

Ń Ń ć ć Ł Ć Ń ć Ę

ć Ś

Ł Ł ń ć Ą

ń ń ń ż ć Ł ż ż ń ż Ą ń Ż ż

ż ń ń ź ź ź

Ż ć ć Ż ź ć ć ż ć ż ć Ż ć Ą ń Ż ć Ę

ń ż ś

Ń ź ź ź ź Ś ź ź Ś ź

Ść ć Ż ć Ż Ś ć ż ń ż Ż ć Ś Ż ń

Ś ź ź Ł Ó Ń

Algorytmy i Struktury Danych, 9. ćwiczenia

Lista, Stos, Kolejka, Tablica Asocjacyjna

Dla człowieka naturalnym sposobem liczenia jest korzystanie z systemu dziesiętnego, dla komputera natomiast korzystanie z zapisu dwójkowego

Pracownia Komputerowa wyk ad V

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Algorytmy w teorii liczb

Ą Ł Ę Ń Ą Ó ŚĆ Ś ć Ó ń ć ŚĆ ć ć

Ó Ó Ę

Ż Ę ź Ó

Ą Ź ć Ń Ą ć Ź Ź

Ż Ź Ź ź Ż Ż Ź Ą Ą Ż ź Ś Ż Ż Ś Ź Ś Ą

ź ź

Ł Ś ś

Ł Ą Ó Ł ć Ą ć ć

Ę Ł ź ź ć ź ć Ń ć ź ź Ł

Ó Ż ż Ć ż ż ż Ó Ę Ę Ó Ó ż Ó Ł ż Ł

Ł Ń ś ń ć Ź ś ń

ń

Wstęp do Informatyki

ć Ą ź ć ć Ż ź ź Ą ź ć ź ć ź

Ę Ę Ę Ś Ł Ł Ł Ś

Ó Ó ć

Ó Ą Ł Ń ń ć ń ń ć Ń Ń ń Ń ń Ń ć ć ć Ń ź ź

ż ś ż ś Ę ś ż ś ś ś Ł ś ż Ł ż ś ś ś ż

ĘŚ ĘŚ Ó Ę

ć ź ć Ó

ść ś ń ś ś ź ś ć Ą ś Ą ś ń ś ń ń ń ń Ń ć ź ń ś ń ń Ń ć ń ś ś

ź Ś Ż Ę Ś

Ó Ń Ś Ą Ś Ń Ś Ś

Ó Ź ż ć Ą ż ż ć Ę ź Ą ż ż ż ż ż

Ś Ś

ó ó ó ó ó ó ń ó ó ó ó ń ó ó ń ń ó ó ó Ś ń ó ń ó ó ó

Ó Ż ć ć ć ć ć ć ć Ę ć ć ć

Ą Ś Ń Ś Ą Ś Ń

Ć ź Ś Ż ź Ę Ś

Transkrypt:

Algorytmy i złożoności Wykład 5. Haszowanie (hashowanie, mieszanie) Wprowadzenie Haszowanie jest to pewna technika rozwiązywania ogólnego problemu słownika. Przez problem słownika rozumiemy tutaj takie zorganizowanie struktur danych i algorytmów, aby można było w miarę efektywnie przechowywać i wyszukiwać elementy należące do pewnego dużego zbioru danych (uniwersum). Przykładem takiego uniwersum mogą być liczby lub napisy (wyrazy) zbudowane z liter jakiegoś alfabetu. Innym rozwiązaniem, które poznamy na dalszych zajęciach są tzw. drzewa BST. Wyobraźmy sobie, że musimy w jakiś sposób przechowywać w programie słowa. Jeżeli chcielibyśmy użyć do tego celu tablicy, to powstanie oczywiście problem, w jaki sposób zamienić (odwzorować) poszczególne słowa na liczby będące indeksami w tej tablicy. Gdybyśmy chcieli ponumerować wszystkie słowa, które występują w języku polskim i następnie używać tych numerów jako indeksów, to tablica musiałaby mieć ogromny rozmiar. Tutaj właśnie rozpoczyna się haszowanie. Polega ono na odwzorowaniu zbioru wszystkich możliwych słów S przy pomocy tzw. funkcji haszującej w stosunkowo niewielką tablicę: H : S 0,, m 1 Oczywiście nie ma cudów: nie da się znaleźć takiej funkcji H, która mogła by odwzorować duży zbiór S w znacznie mniejszy zbiór 0,, m 1 w sposób różnowartościowy. Ale jeżeli dobierze się odpowiednią funkcją H, to sytuacje, w których występują kolizje powinny się zdarzać stosunkowo rzadko. Oczywiście problem kolizji należy jednak uwzględnić. Rozwiązywanie problemu kolizji Metoda I Jest to tzw. metoda łańcuchowa. Dla każdego indeksu i 0,, m 1 przechowujemy w tablicy HashTab początek do listy elementów, które mają tę samą wartość funkcji haszującej. Tak więc nasza struktura może wyglądać tak: 0 1 2... m-1

Metoda II Jest to tzw. metoda adresowania liniowego. Jeżeli dana pozycja H(k) w tablicy HashTab jest już zajęta, to szukamy nowego miejsca pod adresami H(k)+1, H(k)+2,.... Gdy dojdziemy do końca tablicy HashTab (czyli przekroczymy ostatni indeks równy m-1), to zawijamy się na początek tablicy. Można wiec to opisać pseudokodem tak: i = 1; while (((HashTab[(H(k) + i) mod m]) jest zajęta) i++; wstaw element w miejsce HashTab[(H(k) + i) mod m] Wadą tej metody jest tendencja do grupowania się obiektów wokół pierwotnych kluczy. Metoda III Jest to tzw. metoda adresowania kwadratowego. Metoda ta jest prostą modyfikacją adresowania liniowego. Zamiast dodawać kolejne wartości i dodajemy ich kwadraty i 2 : i = 1; while (((HashTab[(H(k) + i*i) mod m]) jest zajęta) i++; wstaw element w miejsce HashTab[(H(k) + i*i) mod m] Okazuje się, że w tym przypadku nie występuje tendencja do grupowania się obiektów wokół pierwotnych kluczy. Jej niewielką wadą jest to, że podczas próbkowania nie są przeszukiwane wszystkie pozycje tablicy HashTab, tj. może się zdarzyć, że przy wstawianiu nie znajdzie się wolnego miejsca, chociaż puste pozycje w tablicy HashTab nadal występują.. Najlepsze efekty uzyskuje się, gdy jako m (rozmiar tablicy HashTab) używamy liczb pierwszych. Przykład 1 Zakładając, że każde słowo możemy zapisać przy pomocy 26 podstawowych liter alfabetu łacińskiego, oszacujemy ile jest takich słów zakładając, że interesują nas słowa nie dłuższe niże 10 znaków oraz rozróżniamy duże i małe litery. Zamiana słów na liczby Spróbujmy zbudować jakąś prostą funkcje haszującą działającą na dziedzinie słów (z literami bez polskich znaków diakrytycznych ą, ć itd.). Jak wiadomo komputery używają różnych sposobów reprezentacji liter jako liczb. Jednym z takich systemów jest standard ASCII, w którym są m. in. zakodowane wszystkie litery alfabetu łacińskiego (duże i małe znaki), cyfry oraz znaki interpunkcyjne. Przykładowo litera a ma kod 97, d 100, A to 65. Obecnie przechodzi się na kodowanie przy pomocy standardu Unicode, w którym znakom przypisuje się liczby dwubajtowe. My jednak stworzymy własny kod, przyjmując: a to 1, b to 2,..., ż to 32. Oto odpowiednia tabela: a b c d e f g h i j k l ł m n o p r s t u w y z ą ę ć ń ó ś ź ż 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Jak możemy teraz przekształcić poszczególne słowa w jedną liczbę? Istnieje oczywiście wiele sposobów; my przeanalizujemy tylko dwa dość reprezentatywne. Przekonamy się również, że

oba mają poważne wady, co uświadomi nas, że tablice haszujące i odpowiednie funkcje haszujące (mieszające) są niezbędne. Przykład 2 (Dodawanie kodów poszczególnych znaków) Najprostszy sposób odwzorowania słowa na liczbę to dodanie kodów wszystkich liter. Tak więc H( kot )=11+16+20=47. Przykład 3 (Mnożenie przez potęgi) Spróbujmy odwzorować słowa na liczby inną metodą. Postarajmy się teraz, aby kodów było więcej. Niech H(x 1 x 2...) = liczba, której kodem o podstawie 32 jest napis x 1 x 2..., gdzie wartości poszczególnych cyfr a, b, itd. są takie jak w powyższej tabeli. Tak więc H( kot ) = 32 2 k+32 1 o+32 0 t = 32 2 11+32 1 16+32 0 20 = 11264+352+20=11636. Przykład 4 (Adresowanie liniowe) Zrealizujemy algorytm haszowania z próbkowaniem liniowym dla zbioru danych, które są liczbami całkowitymi. Dla uproszczenia przyjmijmy, że uniwersum danych do kodowania to S=long (w sensie języka C). W szczególności zdefiniujemy funkcje insert() oraz search(), które odpowiednio wstawiają i wyszukują elementy. Wskazówki W poniższym ogólnym kodzie należy za Item przyjąć typ long. Funkcja mieszająca H() dokonuje pierwszego wyboru indeksu w tablicy HashTab. Można ją różnie definiować. Jako pierwszą najlepiej wybrać proste dzielenie modulo: H(e) = e mod MAX_HASHTAB Deklaracje: const int MAX_HASHTAB =???; // dowolna liczba dodatnia typu int; najlepiej liczba pierwsza const int FREE = -1; // Uwaga! Oznacza to, że nie możemy przechowywać w naszej tabeli // wartości typu long równej -1 Item *HashTab; // dalej będzie alokacja pamięci int n; // do sprawdzania stopnia zapełnienia tablicy Funkcje: int H(long e) return (e % MAX_HASHTAB); void create() HashTab = new Item[MAX_HASHTAB]; for(int i=0; i < MAX_HASHTAB; i++) HashTab[i] = FREE; n = 0; void insert(item e)

int i; i = H(e); while (HashTab[i]!= FREE) i = (i + 1) % MAX_HASHTAB; HashTab[i] = e; n++; int search(item e) int i; i = H(e); while (HashTab[i]!= FREE) if (HashTab[i] == e) return i; else i = (i + 1) % MAX_HASHTAB; return -1; main() long e; cout << Test działania\n ; create(); for (e=7; e < 100000; e += 9) insert(e); Funkcje mieszające dla kluczy napisowych W wielu sytuacjach klucze (elementy słownika) nie są liczbami, lecz długimi ciągami znaków alfanumerycznych. Jak obliczyć funkcję mieszającą dla takiego słowa jak: bardzudlugiklucz? W 7-bitowym kodzie ASCII to słowo odpowiada liczbie: b 128 15 +a 128 14 +...+c 128 1 +z 128 0, która jest zbyt wielka, aby mogła być używana przez standardowe funkcje arytmetyczne komputerów. A oto inny sposób, który po prostu oblicza resztę z dzielenia wartości wyliczonej powyżej przez rozmiar tablicy (m = MAX_HASHTAB): int H1(char* str, int m) int h = 0; a = 127; while (*str!= 0) h = (a*h + *str) % m; str++;

return h; Funkcja H2(), której kod jest podany niżej ma bardziej równomierny rozkład. Oznacza to, że prawdopodobieństwo kolizji pomiędzy dwoma różnymi kluczami zbliża się do 1/m gdzie m jest rozmiarem tablicy mieszania (m = MAX_HASHTAB). Oto kod funkcji: int H2(char* str, int m) int h = 0, a = 31415, b = 27183; while (*str!= 0) h = (a*h + *str) % m; a = a*b % (m-1); str++; return (h < 0)? (h + m) : h;