Algorytm Knutha-Morrisa-Pratta (KMP) to algorytm wyszukiwania ciągów znaków, który jest używany do wydajnego znajdowania wzorca w dużych tekstach. W przeciwieństwie do naiwnego algorytmu wyszukiwania wzorców, który zaczyna od początku wzorca po każdym niezgodnym dopasowaniu, KMP używa struktury wzorca , aby uniknąć zbędnych porównań. Wstępnie przetwarza ciąg wzorca i tworzy tablicę zwaną tablicą Longest Prefix Suffix (LPS), która wskazuje, ile wzorca można ponownie wykorzystać po niezgodności.
W algorytmie naiwnego wyszukiwania wzorca zaczynamy od każdego indeksu w tekście i porównujemy go z pierwszym znakiem wzorca, jeśli pasują, przechodzimy do następnego znaku zarówno w tekście, jak i we wzorcu. Jeśli występuje niezgodność, rozpoczynamy ten sam proces dla następnego indeksu tekstu.
Przykład:
- Dane wejściowe: txt= „aabaacaadaabaaba”, pattern = „aaba”
- Wynik: [0, 9, 12]
- Wzorzec „aaba” występuje w ciągu wejściowym txt trzy razy: zaczynając od indeksów 0, 9 oraz 12
Implementacja algorytmu KMP
Inicjujemy dwa wskaźniki, jeden dla ciągu tekstowego i drugi dla wzorca. Gdy znaki przy obu wskaźnikach są zgodne, zwiększamy oba wskaźniki i kontynuujemy porównanie. Jeśli nie są zgodne, resetujemy wskaźnik wzorca do ostatniej wartości z tablicy LPS, ponieważ ta część wzorca została już dopasowana do ciągu tekstowego. Podobnie, jeśli przeszliśmy cały ciąg wzorca, dodajemy indeks początkowy wystąpienia wzorca w tekście do wyniku i kontynuujemy wyszukiwanie od wartości LPS ostatniego elementu wzorca.
def longest_prefix_suffix(txt): n = len(txt) lps = [0] * n # len przechowuje najdłuższy prefix, któryc jest również sufixem dla poprzedniego indexu length = 0 # lps[0] jest zawsze 0 lps[0] = 0 i = 1 while i < n: # jeśli znaki się zgadzają to zwiększ długość lps if txt[i] == txt[length]: length += 1 lps[i] = length i += 1 # w przypadku gdy się nie zgadzają else: if length != 0: # Zaaktualizuj długość poprzedniego indexu lps, abu uniknąć niepotrzebnych porównań length = lps[length - 1] else: # jeśli nie znaleziono psaującego predixu, ustaw wartość lps[i] = 0 lps[i] = 0 i += 1 return lps def knp_search(txt, pattern): n = len(txt) m = len(pattern) res = [] lps = longest_prefix_suffix(pattern) # Wskaźniki do przechodzenia przez tekst oraz wzorzec i = 0 j = 0 while i < n: # Jeśli znaki się zgadzają, zwiększ oba wskaźniki if txt[i] == pattern[j]: i += 1 j += 1 # Jeśli cały wzorzec jest dopasowany, zapisz indeks początkowy w wyniku if j == m: res.append(i - j) # Użyj LPS poprzedniego indeksu, aby pominąć niepotrzebne porównania j = lps[j - 1] # Jeśli znaki są inne else: # Użyj LPS poprzedniego indeksu, aby pominąć niepotrzebne porównania if j != 0: j = lps[j - 1] else: i += 1 return res txt = "aabaacaadaabaaba" pat = "aaba" index_arr = knp_search(txt, pat) print(index_arr)
- Złożoność czasowa: O(n + m), gdzie n to długość tekstu, a m to długość wzorca. Dzieje się tak, ponieważ utworzenie tablicy LPS (Longest Prefix Suffix) zajmuje O(m) czasu, a przeszukanie tekstu zajmuje O(n) czasu.
- Złożoność pamięciowa: O(m), ponieważ musimy przechowywać tablicę LPS o rozmiarze m.