Haszowanie Jakub Radoszewski.

Slides:



Advertisements
Podobne prezentacje
Tablice 1. Deklaracja tablicy
Advertisements

Instrukcje - wprowadzenie
C++ wykład 2 ( ) Klasy i obiekty.
Standardowa biblioteka języka C++
Wzorce.
Dynamiczne struktury danych Listy, Kolejki, Stosy
Język ANSI C Funkcje Wykład: Programowanie komputerów
Wykład 10 Metody Analizy Programów Specyfikacja Struktur Danych
Standard Template Library
Filip Andrzejewski Remigiusz Chiluta
Implementacja ekstensji klasy
Elementarne struktury danych Piotr Prokopowicz
Ciągi de Bruijna generowanie, własności
Budowa i funkcje elektronicznego katalogu biblioteki szkolnej
ZŁOŻONOŚĆ OBLICZENIOWA
ALGORYTMY GEOMETRYCZNE.
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 7: Procedury i funkcje © Jan Kaczmarek.
WITAM NA SZKOLENIU Porady na dziś i jutro.
Podstawy informatyki Informatyka stosowana Prowadzący: Grzegorz Smyk
Biblioteki i przestrzenie nazw
SO – LAB3 Wojciech Pieprzyca
Algorytmy i struktury danych Funkcje haszujące, wyszukiwanie wzorca
PROJEKTOWANIE TABEL W PROGRAMIE: ACCESS
POJĘCIE ALGORYTMU Pojęcie algorytmu Etapy rozwiązywania zadań
Algorytmy.
Podstawy informatyki (4)
Algorytmy i struktury danych
Podstawy programowania
© A. Jędryczkowski – 2006 r. © A. Jędryczkowski – 2006 r.
Algorytmy i Struktury Danych Typy algorytmów
TABLICE C++.
Algorytmy i struktury danych
Algorytmy i struktury danych
Poznajemy pulpit.
ANNA BANIEWSKA SYLWIA FILUŚ
Edytor Vi.
Projektowanie tabeli w arkuszu kalkulacyjnym
Algorytmy rekurencyjne - przykład
Systemy wejścia i wyjścia Michał Wrona. Co to jest system wejścia i wyjścia? Pobierania informacji ze źródeł danych, zdolnych przesyłać sekwencje bajtów,
Programowanie w języku C++
Projektowanie stron WWW
Algorytmika.
Algorytmy i Struktury Danych
JĘZYKI ASSEMBLEROWE ..:: PROJEKT ::..
K URS JĘZYKA C++ – WYKŁAD 1 ( ) Łagodne wprowadzenie do języka C++
Powtórzenie wyk ł adu 10 Fizyczna organizacja danych w bazie danych. Indeksy.
WYKŁAD 06 Programowanie dynamiczne Grażyna Mirkowska.
Algorytmy i Struktury Danych Struktury Danych
FrontPage Co i jak ???. FrontPage Tworzenie stron internetowych z wykorzystaniem prostych edytorów tekstu (np. Notatnik) jest dość trudne i bardzo pracochłonne.
INTERNET jako „ocean informacji”
Wykład 2 Programowanie obiektowe. Programowanie obiektowe wymaga dobrego zrozumienia działania funkcji definiowanych przez użytkownika, w ten sposób będziemy.
Wstęp do programowania wykład 3 Typy wyliczeniowe, tablice.
Mapa STL – C++. Problem polega na tym, że najczęściej chcielibyśmy przechowywać w zbiorze elementy jakiegoś bardziej złożonego typu, których on nie będzie.
Standard Template Library Czyli Es Te El. Co to jest? Po polskiemu STL to standardowa biblioteka szablonów, czyli biblioteka C++ zawierająca algorytmy,
Podstawy informatyki Preprocesor Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi.
Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego Matuszyka Podstawy.
ALGORYTMY I STRUKTURY DANYCH
STOS. STL (ang. Standard Template Library) jest to biblioteka zawierająca algorytmy, pojemniki, iteratory oraz inne konstrukcje w formie szablonów, gotowe.
C++ mgr inż. Tomasz Turba Politechnika Opolska 2016.
Temat: Tworzenie bazy danych
C++ mgr inż. Tomasz Turba Politechnika Opolska 2016.
Indeksy drzewiaste. ISAM ISAM - INDEXED SEQUENTIAL ACCESS METHOD Problem: –Dany jest plik uporządkowany – w jaki sposób zrealizować efektywnie zapytanie.
C++ mgr inż. Tomasz Turba Politechnika Opolska 2016.
Typy wyliczeniowe, kolekcje
Listy.
Algorytmy i struktury danych
ALGORYTMY I STRUKTURY DANYCH
Język C++ Typy Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego.
PGO Porównywanie obiektów
Zapis prezentacji:

Haszowanie Jakub Radoszewski

Postawienie problemu Poszukujemy struktury danych, z pomocą której moglibyśmy efektywnie wykonywać operacje: wstawianie obiektów, usuwanie obiektów, wyszukiwanie obiektów. Obiekty: liczby, napisy, rekordy z danymi personalnymi. Zastosowania: bazy danych, sieci komputerowe, ...

Obiekty to liczby z przedziału [0, N-1] Szukana struktura to prosta tablica N-elementowa: bool t[N]; Wstawianie: void wstaw(int n) { t[n] = true; } Usuwanie: void usun(int n) { t[n] = false; Wyszukiwanie: bool wyszukaj(int n) { return t[n];

Przypadek ogólny Szukamy funkcji h, przekształcającej obiekty w liczby z przedziału [0,N-1]. h to funkcja haszująca (funkcja skrótu). Obiekty mogą być parami (klucz,wartość), np. (imię i nazwisko, informacje o przelewach bankowych). Funkcja haszująca może operować tylko na kluczach. Mamy teraz tablicę obiektów: obiekt t[N]; Przy wstawianiu wykorzystujemy funkcję h: void wstaw(obiekt o) { int hasz = h(o.klucz); t[hasz] = o; }

Przypadek ogólny cd. Usuwanie wygląda podobnie: (BRAK jest jakąś stałą, oznaczającą brak obiektu) void usun(obiekt o) { int hasz = h(o.klucz); t[hasz] = BRAK; } Wyszukiwać możemy np. po kluczu: obiekt wyszukaj(klucz k) { int hasz = h(k); return t[hasz]; Wniosek: o ile wyznaczanie h(klucz) jest szybkie, to wstawianie, usuwanie i wyszukiwanie też są szybkie!

Funkcje haszujące Pytanie: skąd wziąć odpowiednią funkcję haszującą? Załóżmy, że klucze są (dużymi) liczbami, np. z zakresu -109..109 (typ int w C++). Przykłady wykorzystywanych funkcji haszujących: h(x) = x % p, gdzie p jest pierwsze h(x) = (x*p + r) % q, gdzie p i q są pierwsze h(x) = [((x*A) mod 1)*m], gdzie A jest z przedziału (0,1)

Problem kolizji Liczb z zakresu -109..109 jest znacznie więcej, niż N. Zasada szufladkowa Dirichleta podpowiada, że mogą być kolizje. Chcemy, żeby h była bardzo „losowa”, żeby dobrze rozrzucała klucze. Paradoks urodzin: Jeżeli w jednej sali jest co najmniej (= ok. 19) osób, to z prawdopodobieństwem ½ dwie z nich obchodzą urodziny tego samego dnia. Wniosek: nawet przy bardzo losowej funkcji haszującej, już po wstawieniach elementów jest duża szansa na wystąpienie kolizji! Istnieje kilka sposobów zaradzenia tej sytuacji.

Adresowanie otwarte Pomysł: jeżeli dane miejsce w tablicy jest zajęte, to spróbuj obiekt umieścić gdzieś indziej. Adresowanie liniowe: jeżeli pozycja h(k) jest zajęta, spróbuj umieścić obiekt na pozycji h(k)+1, w razie kolejnej porażki – na pozycji h(k)+2, ... Adresowanie kwadratowe: jeżeli pozycja h(k) jest zajęta, to próbuj (do skutku) umieszczać dany obiekt na pozycjach h(k)+12, h(k)-12, h(k)+22, h(k)-22, h(k)+32, ... Haszowanie dwukrotne (rehaszowanie): stanowi rozszerzenie dwóch pierwszych pomysłów. Rozważane są mianowicie pozycje h(k)+g(k), h(k)+2*g(k), ..., gdzie g jest jakąś inną funkcją haszującą.

Metoda łańcuchowa Dla każdej wartości hasza z przedziału [0,N-1] w tablicy t przechowujemy zbiór obiektów, których klucze mają taką samą wartość hasza. Pytanie: jak reprezentować zbiór obiektów, którego rozmiaru nie potrafimy przewidzieć? Rozwiązanie: dynamiczne struktury danych (np. listy). W C++ da się prościej za pomocą vectora. vector to jeden z kontenerów z biblioteki standardowej C++ (tzw. STL). Zapewne o STL-u nieraz jeszcze usłyszycie.

vector w C++ Potrzebujemy załadować plik nagłówkowy: #include <vector> using namespace std; vector działa jak tablica zmiennego rozmiaru: vector<int> v; - deklaracja vectora intów (ogólnie vector<typ>) UWAGA: vector domyślnie jest pusty! v.push_back(123); - wstawienie elementu 123 na koniec vectora, co powoduje jego powiększenie v.pop_back(); - usunięcie ostatniego elementu (zmniejszenie) v[7] = 5; - działa jak dla tablicy, o ile ósmy element v istnieje int rozm = v.size(); - zwraca aktualny rozmiar vectora if (v.empty()) ... ; - czy vector jest pusty? i wiele innych operacji...

Wykorzystanie vectora Deklaracja tablicy t: vector<obiekt> t[N]; Wstawianie obiektu: void wstaw(obiekt o) { int hasz = h(o.klucz); t[hasz].push_back(o); } Usuwanie obiektu: void usun(obiekt o) { for (int i = 0; i < t[hasz].size(); i++) if (t[hasz][i] == o) { /* Przerzuć na koniec i usuń */ swap(t[hasz][i], t[hasz][t[hasz].size()-1]); t[hasz].pop_back();

Wykorzystanie vectora cd. Wyszukiwanie obiektu: obiekt wyszukaj(klucz k) { int hasz = h(k); for (int i = 0; i < t[hasz].size(); i++) if (t[hasz][i].klucz == k) return t[hasz][i]; return BRAK; } Podsumowanie wykorzystania vectora: korzyści: znika problem kolizji, straty: usuwanie i wyszukiwanie mogą być wolne.

Zastosowania haszowania Problem przechowywania haseł w komputerach: niebezpiecznie to robić w czystym tekście! W komputerze przechowuje się tylko hasze z haseł. Funkcja haszująca jest dosyć skomplikowana (algorytm DES, będący właściwie metodą szyfrowania). Ważne, żeby funkcja haszująca była „losowa” i „nieprzewidywalna”, aby trudno było do danego hasza podać jakiekolwiek dobre hasło (ważne w przypadku przechwycenia maszyny). Tę własność określa się także mianem jednokierukowości.

Zastosowania haszowania Problem zakłóceń w transmisji: przy wysyłaniu dużych plików przez Internet gubią się ich fragmenty (pakiety). Do sprawdzenia poprawności transmisji używa się dodatkowo przesłanego hasza (wyliczonego algorytmem MD5 bądź odmianą algorytmu CRC), będącego sumą kontrolną pliku. Odbiorca wyznacza hasz z otrzymanego pliku i sprawdza, czy wyszedł taki sam jak otrzymany. Hasz musi być małych rozmiarów, żeby zminimalizować transmisję nadmiarowych danych. Funkcja haszująca musi być „losowa”, żeby efekt utraty kilku pakietów z dużą pewnością powodował zmianę jej wartości.

Zastosowania haszowania Problem wyszukiwania wzorca (np. słowa, wyrażenia) w tekście. Pojawia się w edytorach tekstu (np. Word), przeglądarkach internetowych (Internet Explorer, Mozilla Firefox, ...). Tym zajmiemy się dokładniej. Ale najpierw pytanie: Jak wyznaczać hasze z napisów? (np. łańcuchów w C++) Dla napisu postaci char a[N] stosuje się np. funkcję: h(a) = (a[0] + a[1]*p + a[2]*p2 + ... + a[N-1]*pN-1) % q, gdzie p i q są dosyć dużymi liczbami pierwszymi (wtedy funkcja działa najlepiej, czyli minimalizuje liczbę kolizji).

Wyszukiwanie wzorca Algorytm naiwny: sprawdza wszystkie pozycje tekstu, na których mógłby się zaczynać wzorzec: char w[N], t[M]; /* wzorzec i tekst */ for (int i = 0; i <= M – N; i++) { /* Szukamy wystąpienia wzorca na pozycjach i, i+1, ..., i+N-1. */ bool znalazlem = false; for (int j = 0; j < N; j++) if (w[j] != t[i + j]) { jest = false; break; /* Wczesne wyjście z pętli. */ } if (jest) printf(‘’Wystapienie na pozycji %d.\n”, i);

Analiza algorytmu naiwnego Dla losowych danych (w oraz t) zachowuje się świetnie: jest bardzo szybki! Dla specyficznych danych może wykonywać ok. N*M operacji, np. dla danych typu: aaaaaaaaab - tekst aaaab - wzorzec, i=0 aaaab - i=1 aaaab - i=2 aaaab - i=3 aaaab - i=4 aaaab - sukces, i=5

Hasze pomagają wyszukiwać Krok 1: Liczymy hasz dla wzorca w ze wzoru: hasz = (w[0] + w[1]*p + w[2]*p2 + ... + w[N-1]*pN-1) % q za pomocą takiej oto prostej pętli: int hasz = 0; for (int i = N – 1; i >= 0; i--) hasz = (hasz * p + w[i]) % q; To działa, bo kolejno otrzymywane wartości hasza to: 0, (w[N-1])%q, (w[N-2] + w[N-1]*p)%q, (w[N-3] + w[N-2]*p + w[N-1]*p2)%q, ... Ciekawostka: ta metoda nazywa się schematem Hornera.

Hasze pomagają wyszukiwać Krok 2: liczymy hasze dla tekstu, ale tym razem spamiętujemy wszystkie wartości pośrednie w tablicy: int hasze[M + 1]; hasze[M] = 0; for (int i = M – 1; i >= 0; i--) hasze[i] = (hasze[i + 1] * p + t[i]) % q; Zawartość tablicy hasze to teraz: (podobnie jak dla wzorca) hasze[0] = (t[0] + t[1]*p + t[2]*p2 + ... + t[M-1]*pM-1) % q, hasze[1] = (t[1] + t[2]*p + ... + t[M-1]*pM-2) % q, ... hasze[M-2] = (t[M-2] + t[M-1]*p) % q, hasze[M-1] = (t[M-1]) % q, hasze[M] = 0.

Hasze pomagają wyszukiwać Jak teraz sprawdzić, czy wzorzec w występuje w tekście t na pozycjach i, i+1, ..., i+N-1? Musimy policzyć jakoś hasz dla słowa t[i..i+N-1] i porównać go z haszem dla w. Szukanym haszem okazuje się być wartość: (hasze[i] – hasze[i+N]*pN) % q = ((t[i] + t[i+1]*p + ... + t[i+N]*pN + t[i+N+1]*pN+1 + t[M-1]*pM-1-i) – (t[i+N] + t[i+N+1]*p + ... + t[M-1]*pM-1-i-N)*pN) % q = (t[i] + t[i+1]*p + ... + t[i+N-1]*pi+N-1) % q.

Ostateczna postać wyszukiwania Oto ostateczny pseudokod algorytmu wyszukiwania wzorca w tekście z wykorzystaniem haszy (zakładamy, że policzyliśmy już wartość hasz dla wzorca oraz tablicę hasze dla tekstu): pN = 1; /* Chcemy, by to było równe pN */ for (int i = 1; i <= N; i++) pN = (pN * p) % q; for (int i = 0; i <= M – N; i++) { /* Szukamy wystąpienia wzorca na pozycjach i, i+1, ..., i+N-1. */ if (hasz == (hasze[i] – hasze[i + N] * pN) % q) printf(‘’Wystapienie na pozycji %d.\n”, i); } Wyszukiwanie zajmuje około M-N operacji, a samo liczenie haszy: łącznie około N+M operacji. To dużo lepiej niż wynik algorytmu naiwnego! Za to ryzykujemy możliwość kolizji...

Uwagi końcowe Przykład dobrych liczb pierwszych: p = 70032301, q = 1000000007 W takim przypadku mogą wystąpić problemy przy mnożeniu (np. hasze[i + N] * pN), gdyż typ int się przepełnia (wynikiem może być liczba rzędu nawet 1018! ) Rozwiązaniem jest zastosowanie przy mnożeniu większego typu, np. long long: (long long)(hasze[i + N]) * pN który ma dokładność jeszcze troszkę większą niż 1018.