KMP Algoritması
Desenin önek tablosunu kullanarak metinde hiç geri dönmeden O(n + m) zamanda arayan Knuth-Morris-Pratt algoritması.
Önce bunlara göz at
#Nasıl çalışır?
Knuth-Morris-Pratt (KMP) algoritması, düz aramanın tekrar tekrar aynı harfleri karşılaştırma sorununu çözer. Uyuşmazlık olduğunda metinde asla geri dönmez; zaten tutan harflerden yararlanarak deseni ileri kaydırır.
Bunun için önce desenden bir önek tablosu (lps, "longest proper prefix which is also suffix") hesaplanır: lps[j], desenin ilk j + 1 harfinde hem önek hem sonek olan en uzun parçanın uzunluğudur. ABABCABAB için tablo 0 0 1 2 0 1 2 3 4'tür.
j harf tuttuktan sonra bir uyuşmazlık çıkarsa, tutan kısmın son lps[j − 1] harfi desenin başıyla zaten aynıdır. Desen buna göre kaydırılır ve karşılaştırma oradan sürer. Böylece metindeki her harf en fazla iki kez karşılaştırılır.
#Kendin dene
| A | B | A | B | C | A | B | A | B | |
|---|---|---|---|---|---|---|---|---|---|
| lps | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 |
KMP önce desenin önek tablosunu (lps) kurar. Uyuşmazlıkta metinde geri dönmez; deseni tablonun söylediği kadar kaydırır.
#Adım adım
- 01Desenin önek tablosunu (lps) hesapla.
- 02Metin ve desen işaretçileriyle ilerle; harfler eşitse ikisini de ilerlet.
- 03Desen tamamen tuttuysa eşleşmeyi kaydet ve j = lps[j − 1] yap.
- 04Uyuşmazlıkta j > 0 ise j = lps[j − 1]; j = 0 ise metinde bir harf ilerle.
#Örnek kod
Aynı algoritma 8 dilde. Her program derlenip çalıştırılarak test edildi ve hepsi aynı çıktıyı veriyor.
def prefix_table(pattern: str) -> list:
"""lps[i] = longest proper prefix of pattern[:i + 1] that is also its suffix."""
lps = [0] * len(pattern)
length, i = 0, 1
while i < len(pattern):
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
elif length > 0:
length = lps[length - 1]
else:
i += 1
return lps
def kmp_search(text: str, pattern: str) -> list:
lps = prefix_table(pattern)
found, i, j = [], 0, 0
while i < len(text):
if text[i] == pattern[j]:
i += 1
j += 1
if j == len(pattern):
found.append(i - j)
j = lps[j - 1]
elif j > 0:
j = lps[j - 1] # slide the pattern; i never moves back
else:
i += 1
return found
print(" ".join(map(str, prefix_table("ABABCABAB"))))
print(" ".join(map(str, kmp_search("ABABDABACDABABCABAB", "ABABCABAB"))))#Karmaşıklık
| İşlem | Süre |
|---|---|
| Önek tablosu | O(m) |
| Arama | O(n) |
| Toplam (en kötü durumda bile) | O(n + m) |
| Bellek | O(m) |
Metin işaretçisi hiç geri gitmez ve her uyuşmazlık deseni en az bir konum kaydırır; bu yüzden toplam karşılaştırma sayısı 2n'yi geçmez. Önek tablosu da aynı fikirle O(m)'de kurulur.
#Ne zaman kullanılır?
Uzun metinlerde ve tekrarlı içerikte garantili doğrusal arama: DNA dizileri, log dosyaları, akış (stream) halinde gelen veride desen arama. Önek tablosu fikri ayrıca bir dizgideki en kısa tekrar eden birimi bulmak gibi problemlerde de kullanılır.
#Alıştırma
Soruları cevaplamak ve ilerlemeni kaydetmek için giriş yap.
Kolay
KMP'nin düz aramaya göre temel üstünlüğü nedir?
- 1Metinde asla geri dönmez; en kötü durumda bile O(n + m) sürer
- 2Ön hazırlık gerektirmez
- 3Daha az bellek kullanır
- 4Yalnızca bir eşleşme arar
lps[j] neyi tutar?
- 1Desenin ilk j + 1 harfinde hem önek hem sonek olan en uzun parçanın uzunluğu
- 2j. harfin metindeki konumu
- 3Desenin uzunluğu
- 4j'ye kadar olan harf sayısı
ABAB deseninin önek tablosu nedir?
- 10 0 1 2
- 20 1 2 3
- 30 0 0 0
- 41 2 1 2
Orta
j = 4 harf tuttuktan sonra uyuşmazlık çıkarsa ve lps[3] = 2 ise j ne olur?
- 12
- 20
- 33
- 44
Zor
KMP'de metindeki her harf en fazla kaç kez karşılaştırılır?
- 1Yaklaşık iki kez; toplam karşılaştırma 2n'yi geçmez
- 2m kez
- 3Bir kez
- 4log m kez