KMP學習筆記
zsy說過:”一切字串演算法的本質都是有效利用失配資訊進行匹配或查詢!“
一、 KMP是什麼
給出兩個字串 \(s1\) 和 \(s2\),若 \(s1\) 的區間 \([l,r]\) 子串與 \(s2\) 完全相同,則稱 \(s2\) 在 \(s1\) 中出現了,其出現位置為 \(l\)。
現在請你求出 \(s2\) 在 \(s1\) 中所有出現的位置。
(我們把 \(s2\) 稱作模式串,把 \(s1\) 稱為文字串, \(s1\) 可能非常長)
二、 暴力
顯然,我們可以兩層迴圈,列舉起始位置,然後一一比較,如果失配就 continue 到下一個位置。它的時間複雜度是 \(O(nm)\)
for i:=1 to n do
begin
for j:=1 to m do
begin
if a[i+j-1]<>b[j] then break;
if j=m writeln(i);
end;
end;
三、 優化暴力
考慮這個暴力慢在什麼地方:每一次失配後, \(i\) 和 \(j\) 兩個指標都要回跳。如果能保證 \(i\) 指標單調不減,只移動 \(j\) 指標,就會快很多——時間複雜度變成了 \(O(n+m)\) 。
這當然是可行的,觀察下面這組樣例:
A B A C
A B A B A C A
在第4位失配後,如果只移動模式串,可以直接把模式串的第2位移動到與文字串的第4位對齊,如下:
A B A C
A B A B A C A
這是用肉眼看出來的結果,怎麼讓程式知道,在第 j 位失配後,應該跳到第幾位?能否預處理出一個移動陣列?需要滿足什麼性質?
為保證正確性,這個 \(nxt\) 陣列有如下三條性質:
-
nxt[1]=0
; -
s2[nxt[j]]=s2[j]
; -
s2[1~nxt[j]]
與s2[j-nxt[j]+1]~j]
一一相等.
如果得到了這個陣列,在 \(i\) 和 \(j+1\) 失配時,就只需使 j=nxt[j]
,\(i\) 不需改變。
四、 程式碼實現
假裝已經知道了 \(nxt\) 陣列,用下面這段程式碼就能輕鬆求出 \(s2\)
j:=0;
for i:=1 to length(a) do
begin
while(j>0) and (a[i]<>b[j+1]) do
j:=nxt[j];
if a[i]=b[j+1] then inc(j);
if j=length(b) then
begin
writeln(i-length(b)+1);
j:=nxt[j];
end;
end;
怎麼求出 \(nxt\) 陣列呢?用模式串跟自己匹配即可,只需把最後一個 if 語句改為 nxt[i]:=j
。程式碼如下:
nxt[1]:=0;
for i:=2 to length(b) do
begin
while(j>0) and (b[i]<>b[j+1]) do
j:=nxt[j];
if b[i]=b[j+1] then inc(j);
nxt[i]:=j;
end;
這樣做是正確的,因為跑到 nxt[i]:=j
這句話,說明前面一定已經匹配好了,符合前文的兩條性質。
求這個竟然也有模板:P4391 [BOI2009]Radio Transmission 無線傳輸
五、 AC 程式碼
var
a,b:ansistring;
i,j:longint;
nxt:array[0..1000010] of longint;
begin
readln(a);
readln(b);
nxt[1]:=0;
for i:=2 to length(b) do
begin
while(j>0) and (b[i]<>b[j+1]) do
j:=nxt[j];
if b[i]=b[j+1] then inc(j);
nxt[i]:=j
end;
j:=0;
for i:=1 to length(a) do
begin
while(j>0) and (a[i]<>b[j+1]) do
j:=nxt[j];
if a[i]=b[j+1] then inc(j);
if j=length(b) then
begin
writeln(i-length(b)+1);
j:=nxt[j];
end;
end;
for i:=1 to length(b) do
write(nxt[i],' ');
writeln;
end.