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.