KMP自動機


KMP自動機

分類:字符串

內容:詳細版

前置知識

不會的可以點擊鏈接(如果有)或者前往 OI-Wiki 學習

  • KMP

一些約定

  • 字符集大小默認為m
  • 模板字符串默認為s
  • 文本字符串默認為t
  • |s|指字符串s的長度
  • 字符串下標默認從1開始

簡介

KMP自動機主要用於字符串的匹配問題,預處理復雜度為O(|s|*m),可以以嚴格O(|t|)的復雜度進行字符串匹配(KMP為均攤O(|t|),並且可以處理可持久化字符串匹配問題

同時KMP自動機也是AC自動機(可以處理多個模板串的匹配)的基礎。

構造KMP自動機

KMP自動機與KMP的區別在於KMP自動機額外求出了 \(trans_{i,j}\) 表示在第i位置上往后匹配一個j字符會轉移到什么狀態(狀態在這里指已經成功匹配了多少個字符)。

在下文中,將用fail來代替KMPnextnxt代替上面的trans。

普通地實現KMP自動機

假設我們處理到了第i個狀態並且前i-1個狀態已經完全處理好了,當前的fail也指向了正確的位置。

考慮每個nxt指向的狀態:

nxt[s[i + 1]]顯然指向i+1

其余的nxt應當指向一直跳fail后第一個下一個字符能匹配的位置,即:

nxt[i][j] = i;
while(nxt[i][j] && s[nxt[i][j] + 1] != j) nxt[i][j] = fail[nxt[i][j]];
if(s[nxt[i][j] + 1] == j) nxt[i][j] = nxt[i][j] + 1;

但是這樣我們沒有用到之前求出來的nxt並且復雜度很高,所以我們需要找到一種能用到之前求好了的nxt來快速計算當前nxt的方法。

考慮nxt[fail[i]][j],這個表示的是fail[i]這個狀態匹配一個j字符會轉移到什么狀態,即我們想在第i個狀態后接一個j字符,但是s[i + 1]不是這個字符,我們就在fail[i]這個狀態后面接着找並且找到了一個狀態可以轉移。

仔細分析一下這個東西就是我們要求的nxt[i][j]

證明一下:由於nxt[fail[i]][j]要么是從nxt[fail[fail[i]]][j]轉移過來的,已經考慮過考慮跳多次fail,要么是直接在fail[i]后面接一個j字符,不需要跳多次fail,所以不用管跳多次fail,那么nxt[fail[i]][j]就是我們要求的nxt[i][j]了。

// 假設字符串長度為 n,字符集大小為 m
// nxt 一開始都是 0
fail[1] = 0;
nxt[0][s[1]] = 1;
for(int i = 1; i < n; i ++) {
    for(int j = 0; j <= m; j ++) {
        if(s[i + 1] == j) nxt[i][j] = i + 1;
    	else nxt[i][j] = nxt[fail[i]][j];
    }
}
for(int i = 0; i <= m; i ++)
   	nxt[n][i] = nxt[fail[n]][i];

這樣寫出來又長細節又多,一下沒搞好就錯了,最重要的是不好背,我們想辦法縮成單獨一個for循環。

好寫又好背的板子

首先我們處理第i個狀態時,我們可以先讓所有的nxt[i]都是nxt[fail[i]],然后在第i+1個狀態再把nxt[i][s[i+1]]設為i+1,這樣我們就可以不用把第n個狀態單獨拿出來

然后我們可以發現在求nxt[i]的時候不需要用到之前的fail所以我們可以不記錄所有的fail

for(int i = 1, fail = 0; i <= n; i ++) {
	fail = nxt[fail][s[i]]; // 注意這一行不能和下一行互換
	nxt[i - 1][s[i]] = i;
	for(int j = 0; j < m; j ++)
		nxt[i][j] = nxt[fail][j];
}

字符串匹配

構造完以后匹配就很簡單了,直接一直沿着nxt走就行了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM