1. Algorytm KMP (Knuth, Morris Pratt). - prz
Transkrypt
1. Algorytm KMP (Knuth, Morris Pratt). - prz
Instrukcja IEF Algorytmy i struktury danych Laboratorium 2: ”Przeszukiwanie tekstów” cd. 1. Algorytm KMP (Knuth, Morris Pratt). Algorytm KMP opublikowany został w roku 1976 i stanowi rozwinięcie algorytmu naiwnego. Algorytm naiwny wykonuje wiele powtarzających się przeszukiwań, nie wykorzystując w przypadku niezgodności któregoś znaku wzorca informacji wcześniej przebadanej. W algorytmie KMP wykorzystywana jest informacja o możliwości wykonania przesunięcia wzorca o więcej niż jeden znak. i – indeks tekstu j – indeks wzorca N – długość tekstu M - długość wzorca. • tworzenie tablicy przesunięć Tablica przesunięć (zwyczajowo nazwana next) wykorzystywana jest w algorytmie KMP do wyznaczania maksymalnych bezpiecznych przesunięć wzorca względem przeszukiwanego tekstu. Tworzona jest według następującego algorytmu: a) next[0]=-1 j=1 b) umieść kopię pierwszych j znaków wzorca pod fragmentem wzorca złożonym z pierwszych j liter wzorca, przesuniętych w prawo o jeden znak c) przesuwaj te kopię w prawo do chwili gdy: - wszystkie nakładające znaki są identyczne, lub - nie ma nakładających się znaków d) next[j] jest równe liczbie nakładających się znaków j=j+1 jeśli j jest równe długości wzorca – stop, w przeciwnym wypadku skocz do punktu b). • a) b) c) d) e) next[0]=-1; for(i=0,j=-1;i<M-1;i++,j++,next[i]=j) while((j>=0)&&(wzor[i]!=wzor[j])) j=next[j]; algorytm KMP - wykorzystanie tablicy przesunięć i=0 j=0 wyznaczyć tablicę przesunięć next dla danego wzorca dopóki text[i]=wzor[j] i nie przeszukano całego tekstu zwiększ i oraz j o jeden jeśli przeszukano cały tekst, wtedy możliwe są dwa przypadki: jeśli j=M to wzorzec zaczyna się na pozycji i-M, w przeciwnym przypadku wzorzec nie został znaleziony ponieważ tekst[i]!=wzor[j], więc należy skoczyć do c) zmieniając j na next[j]. Jeśli j=-1, należy zwiększyć i oraz j o jeden i również skoczyć do c). for(i=0,j=0;i<N && j<M; i++, j++) while((j>=0) && (tekst[i]!=wzor[j])) j=next[j]; if(j==M) return i-M; else return -1; Przykład Znaleźć wzorzec BABAABBB w tekście BABABAABBABAABBB indeks T i tekst T wzorzec W j indeks W next 0 ↓ B B ↑ 0 -1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A A B B A A B A A B A B B B B A B A A B B B 1 0 2 0 3 1 4 2 5 0 6 1 7 1 Ponieważ znaki text[i] i wzor[j] są zgodne, następuje przesunięcie i oraz j aż do momentu stwierdzenia niezgodności lub znalezienia całego wzorca. W przykładzie niezgodność występuje na pozycji 4: indeks T i tekst T wzorzec W j indeks W next 0 1 2 3 B B A A B B A A 0 -1 1 0 2 0 3 1 4 ↓ B A ↑ 4 2 5 6 7 8 9 10 11 12 13 14 15 A B A B B B B A B A A B B B 5 0 6 1 7 1 Z tego powodu konieczne jest przesunięcie wzorca tak, aby znak o indeksie next[4] (czyli 2), oznaczony pismem pogrubionym znalazł się pod znakiem o indeksie i (oznaczonym kolorem szarym): indeks T i tekst T wzorzec W j indeks W next 0 1 2 3 B A B B A A 0 -1 1 0 4 ↓ B B ↑ 2 0 5 6 7 8 9 10 11 12 13 14 15 A A A A B B B B A B B A A B B B 3 1 4 2 5 0 6 1 7 1 Ponieważ znaki tekstu o indeksach od 4 do 8 są zgodne z odpowiednimi znakami (od 2 do 6) wzorca, więc następuje przesunięcie indeksów i oraz j zgodnie z c). Różne znaki występują na pozycji 9: indeks T i tekst T wzorzec W j indeks W next 0 1 2 3 4 5 6 7 8 B A B B A A B B A A A A B B B B 0 -1 1 0 2 0 3 1 4 2 5 0 6 1 9 ↓ A B ↑ 7 1 10 11 12 13 14 15 B A A B B B Z tego powodu konieczne jest przesunięcie wzorca (poprzez zmianę j) tak, aby znak o indeksie next[7] (czyli 1), oznaczony pismem pogrubionym znalazł się pod znakiem o indeksie i (oznaczonym kolorem szarym): indeks T i tekst T wzorzec W j indeks W next 0 1 2 3 4 5 6 7 8 B A B A B A A B B B 0 -1 9 ↓ A A ↑ 1 0 10 11 12 13 14 15 B B A A A A B B B B B B 2 0 3 1 4 2 5 0 6 1 7 1 Ponieważ wszystkie pozostałe znaki są zgodne z wzorcem, wzorzec został odnaleziony na pozycji 16-8=8. Wartość 8 wynika z wartości indeksu i o jeden większej od długości napisu. 2. Algorytm BM (Boyera i Moore’a) W algorytmie tym w przeciwieństwie do algorytmu KMP porównywany jest ostatni znak wzorca. Jeśli badany znak ( o indeksie i) tekstu nie wchodzi w skład wzorca, możliwe jest przesunięcie indeksu i o długość wzorca. Jeśli znak ten występuje – konieczne jest przesunięcie wzorca tak, aby znak ten znalazł się pod odpowiadającym mu znakiem wzorca. K - maksymalna ilość różnych znaków w tekście i we wzorcu (zakładamy, że występują znaki kodu ASCII) • tworzenie tablicy przesunięć for(i=0;i<K;i++) shift[i]=M; for(i=0;i<M;i++) shift[wzor[i] ]=M-i-1; Przykład: wzorzec ABGBD tekst ABCDEFGH A B G B D 0 1 2 3 4 długość wzorca wynosi 5 Zawartość tablicy shift k ‘A’ ‘B’ ‘C’ ‘D’ ‘E’ ‘F’ ‘G’ ‘H’ shift[k] 4 1 5 0 5 5 2 5 • algorytm BM - wykorzystanie tablicy przesunięć a) wyznaczyć tablicę przesunięć shift dla danego wzorca b) jeśli długość wzorca > długość tekstu – stop, nie znaleziono c) rozpocząć przeszukiwanie od ostatniej litery wzorca i odpowiadającej jej literze tekstu i=M-1, j=M-1 d) dopóki j>=0 wykonuj dopóki i-ty znak tekstu oraz j-ty znak wzorca różnią się, wykonuj używając tablicy shift wyznacz przesunięcie x odpowiadające znakowi T[i] jeśli M-j>x, zwiększ i o wartość M-j; w przeciwnym przypadku – zwiększ i o wartość przesunięcia x jeśli i jest większe lub równe od długości tekstu – stop, wzorca nie znaleziono ustaw j na ostatni znak wzorca zmniejsz i oraz j o jeden e) odnaleziono wzorzec na pozycji i+1 for(i=M-1,j=M-1;j>=0 ; i--, j--) while(tekst[i]!=wzor[j]) { x=shift[tekst[i]]; if(M-j>x) i+=M-j; else i+=x; if(i>=N) { return -1; } j=M-1; } return i+1; Przykład: wzorzec: ABGBD tekst: ABGHHABGBDEH M=5 N=12 M ≤ N - poszukiwania można rozpocząć od i=j=4 indeks T 0 1 2 3 4 5 6 7 8 9 10 11 i ↓ tekst T A B G H H A B G B D E H wzorzec W A B G B D j ↑ indeks W 0 1 2 3 4 Ponieważ j>0 i znaki H i D różnią się, więc należy wyznaczyć przesunięcie x=shift[‘H’]=5. Ponieważ warunek 5-4>5 nie jest spełniony i=4+5=9; indeks T 0 1 2 3 4 5 6 7 8 9 10 11 i ↓ tekst T A B G H H A B G B D E H wzorzec W A B G B D j ↑ indeks W 0 1 2 3 4 Ponieważ i-ty znak tekstu i j-ty znak wzorca są identyczne, więc należy zbadać poprzedni znak, zmniejszając je o jeden: indeks T 0 1 2 3 4 5 6 7 8 9 10 11 i ↓ tekst T A B G H H A B G B D E H wzorzec W A B G B D j ↑ indeks W 0 1 2 3 4 Postępując analogicznie okaże się, że wzorzec został odnaleziony. 3. Zadania do wykonania: Proszę napisać: • funkcję, która znajduje długość łańcucha: int długosc(char*lan); • funkcję, która wypełnia tablicę przesunięć tradycyjnie nazwaną next o rozmiarze równym długości wzorca: int * init_next(char*wzor); • funkcję, która wypełnia tablicę przesunięć tradycyjnie nazwaną shift o rozmiarze równym 256: void init_shift(char*w, int*shift); • funkcje, które znajdują jeden wzorzec w tekście, zwraca indeks lub -1 jeśli go brak : int KMP(char* tekst, char * wzor, int *next); int BM(char *tekst,char*wzor); • funkcję, która znajduje wszystkie wzorce występujące w tekście, zwraca wskaźnik do tablicy indeksów (wewnątrz funkcji dynamiczna rezerwacja pamięci dla tablicy indeksów) oraz udostępnia ich ilość przez wskaźnik : int * KMP_w(char* tekst, char * wzor, int * next, int * ilosc); int* BM_w(char *tekst, char*wzor, int *ilosc); • funkcję main, w której należy wczytać dwa dowolne łańcuchy, wywołać funkcje: init_next oraz KMP, BM i wypisać wynik (czy znaleziono wzorzec, jeśli tak, to od jakiego indeksu się zaczyna). Jeśli znaleziono wzorzec, należy wywołać funkcję KMP_w i również wypisać odpowiednie wyniki (ilość znalezionych wzorców i ich indeksy). Następnie powtórzyć dla metody Boyera i Moore’a: init_shift, BM, BM_w. Operacje: wczytania łańcuchów, wywołania funkcji i wypisywania wyników przeprowadzać dopóki wzorzec znajduje się w tekście.