1. 程式人生 > 其它 >KMP學習筆記

KMP學習筆記

學習KMP演算法時的心得。這是一個用於字串匹配的演算法,通過nxt[]陣列記錄失配資訊,從而達到了O(n)的時間複雜度。

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\) 陣列有如下三條性質

  1. nxt[1]=0 ;

  2. s2[nxt[j]]=s2[j] ;

  3. s2[1~nxt[j]]s2[j-nxt[j]+1]~j] 一一相等.

如果得到了這個陣列,在 \(i\)\(j+1\) 失配時,就只需使 j=nxt[j]\(i\) 不需改變。

四、 程式碼實現

假裝已經知道了 \(nxt\) 陣列,用下面這段程式碼就能輕鬆求出 \(s2\)

\(s1\) 的位置。

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 程式碼

P3375 【模板】KMP字串匹配

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.

THE END