1. 程式人生 > 其它 >【演算法筆記】KMP和AC自動機

【演算法筆記】KMP和AC自動機

KMP

KMP是一種字串匹配演算法,也可以叫它模式匹配演算法。

作用大概是判斷一個字串 \(S \ ,len=n\) 是否是字串 \(T \ ,len=m\) 的字串,並且找出 \(S\)\(T\) 當中每一次出現的位置。

要使用這個演算法必須先知道一個十分重要的思想:\(\text{next}\) 陣列。

\(\text{next}\) 指標

在跑KMP之前需要對於 \(S\) 處理出這個東西(當然,這個東西會出現在很多字串題裡)。

它是一個類似於 AC 自動機的 \(\text{fail}\) 指標的東西(都是在失配時用來轉移)。

但是 \(\text{next}_i\) 是求字首 \(S[1,i]\)

的最長的 Border (同時為原串字首和字尾的一個子串)。

AC 自動機的 \(\text{fail}\) 之後會說。

  • 定義:\(\text{next}_{i}=\max\{j\ |\ S[1,j]=S[i-j+1,i] \ , j\le i\}\)

    說白了,\(\text{next}_i\) 就是對於 \(S\) 的字首 \(S[1,i]\) ,能夠把這個字首分成兩個完全相等的部分 \(S[1\sim j]\)\(S[i-j+1 \sim i]\) 的最大位置 \(j\)

    如果不存在這樣子的 \(j\) ,那麼 \(\text{next}_i=0\)

為了方便敘述,定義這個滿足條件 \(S[1,j]=S[i-j+1,i]\)

\(j\)\(\text{next}_i\) 的"候選項"。

你可以使用樸素的 \(\text{O}(n^2)\)演算法來計算,但是這很明顯不夠啊,我們還不如不用它,直接暴力匹配 \(S\)\(T\) 得了。

那麼這個時候就有一個引理:

\(j\)\(\text{next}_i\) 的一個候選項,那麼滿足 \(j^\prime < j\) 的最大的 \(\text{next}_i\) 的"候選項" \(j^\prime\)\(\text{next}_j\)

證明使用反證法+畫圖即可。

也就是說 \((\text{next}_j,j)\) 這個區間當中的數都不是 \(\text{next}_i\)

的“候選項”。

用它可以對求 \(\text{next}\) 的過程進行優化。

根據它可以發現,對於 \(\text{next}_i\) ,他的所有候選項是 \(\text{next}_i,\text{next}_{\text{next}_i},\text{next}_{\text{next}_{\text{next}_i}}...\)

然後又有一個引理2:

如果 \(j\)\(\text{next}_i\) 的一個“候選項”,那麼 \(j-1\) 一定是 \(\text{next}_{i-1}\) 的”候選項“。

因為如果 \(S[1,j]=S[i+j-1,i]\) ,那麼肯定會有 \(S[1,j-1]=S[i+(j-1)-1,i]\)

(畫圖即可證明)

那麼我們在計算完 \(\text{next}_{i-1}\) 之後,我們讓 \(\text{next}_i\) 的所有候選項變成 \(\text{next}_{i-1}-1,\text{next}_{\text{next}_{i-1}}-1,\text{next}_{\text{next}_{\text{next}_{i-1}}}-1...\) 就可以了。

複雜度就變成了線性的。

實現

在有了 \(\text{next}\) 之後,我們完成了對 \(S\) 的自行匹配。

現在還需要做一個類似的過程,讓 \(S\)\(T\) 進行匹配。

我們定義 \(f_i=\max\{j \ | \ T[i-j+1,i]=S[1,j] \ ,j \le i\}\)

如果 \(f_i=n\) 那麼就證明,\(S\)\(T\) 中出現了一次,並且它出現在區間 \(T[i-n+1,i]\)

我們類比上面 \(\text{next}\) 的求法就能求出 \(f\)

\(\text{next}\) 的求法說通俗點就是:

假設 \(\text{next}_{i-1}\) 已經求出來,那麼嘗試一直擴充套件這個 \(j\)

如果說下一個位置 \(j+1\) 無法滿足 \(S[j+1]=S[i]\) ,那麼令 \(j=\text{next}_j\) (失配),直到 \(j=0\) 再停止。

如果說下一個位置是滿足要求的,那麼令 \(j=j+1\)

複雜度是 \(\text{O}(n+m)\)


#include<bits/stdc++.h>
using namespace std;

const int si=1e6+10;
int n,m;
string s,t;
int Next[si],f[si];

int main(){
	Next[1]=0;
	cin>>s>>t;
	n=(int)s.size(),m=(int)t.size();
	s=' '+s,t=' '+t;
	for(register int i=2,j=0;i<=n;++i){
		while(j>0 && s[i]!=s[j+1]) j=Next[j];
		if(s[i]==s[j+1]) ++j;
		Next[i]=j;
	}// calc next
	for(register int i=1,j=0;i<=m;++i){
		while(j>0 && (j==n || t[i]!=s[j+1])) j=Next[j]; // 這裡把 j==n 放到裡面了,有些要你求 Border 的題要提出來寫(比如lg3375)
		if(t[i]==s[j+1]) ++j;
		f[i]=j;
		if(f[i]==n){
			// S exist in T.
		}
	}// calc f
}

AC自動機

咕咕咕

Reference

《演算法競賽進階指南》

AC 自動機 - OI Wiki

本文來自部落格園,作者:black_trees,轉載請註明原文連結:https://www.cnblogs.com/BTeqwq/p/kmp-acautomaton.html