KMP神在哪里?
子串匹配问题,拍脑袋一下子想出来的暴力解法大抵都是两重for循环,不断重复扫描主串,与子串进行匹配,重复换句话讲就是冗余,会有很高的时间复杂度
我先前博客大作业发的模糊查找算法就是如此,我那里是在计算一个匹配度的问题,通过相同定位到相同字母判定开始计算相同字母的个数作为匹配度,多个判定点时,最终取最值。
而KMP通过对子串分析得到一个DFA(确定有限状态自动机)数组(后面简称dp数组),可以实现“移动”子串来与主串进行匹配,时间复杂度只需 O(N),也就是说只用遍历一遍主串!
关于DFA数组
什么是状态机
状态机(一般指有限状态机或有限状态自动机)是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型
状态机中有几个术语:state(状态) 、transition(转移) 、action(动作) 、transition condition(转移条件) 。
state(状态) :将一个系统离散化,可以得到很多种状态,当然这些状态是有限的。例如:门禁闸机可以划分为开启状态、关闭状态;电扇可以划分为关、一档、二档、三档等状态。
transition(转移) :一个状态接收一个输入执行了某些动作到达了另外一个状态的过程就是一个transition(转移)。定义transition(转移)就是在定义状态机的转移流程。
transition condition(转移条件) :也叫做Event(事件),在某一状态下,只有达到了transition condition(转移条件),才会按照状态机的转移流程转移到下一状态,并执行相应的动作。
action(动作):在状态机的运转过程中会有很多种动作。如:进入动作(entry action)[在进入状态时进行]、退出动作(exit action)[在退出状态时进行]、转移动作[在进行特定转移时进行]。
DFA状态机
我们这里的dp数组就是一个状态机
=> 二维数组 dp[ 状态 ][ 对应到子串中的字符时 ] = 转移到下一个状态
这里的“转移到下一个状态”就是对上面说的“移动”子串来匹配主串的实现
dp[j][c] = next
0 <= j < M,代表当前的状态
0 <= c < 256,代表遇到的字符(ASCII 码)
0 <= next <= M,代表下一个状态
dp[4]['A'] = 3 表示:
当前是状态 4,如果遇到字符 A,
pat 应该转移到状态 3
dp[1]['B'] = 2 表示:
当前是状态 1,如果遇到字符 B,
pat 应该转移到状态 2
如何构建Transition
下图为一个pat = “ABABC”的子串的状态转移图的构建
难点在于怎么让状态机确定回退到之前的哪个状态?
KMP算法强就强在它能尽可能少的回退
如何尽可能少的回退
我们记录一个X状态来标记遇到具体字符后 => 需要回退时,回退到哪个状态
这里对于X状态的标志有点动态规划的思想在里面 => 因为都是根据已知状态来得到新状态
X状态的更新很像匹配子子串
=> X也在匹配子串,X状态此刻匹配出的子串 具有 j状态此刻匹配出来最长相同部分(前缀)
甚至,还可以这样去理解X状态:有点像笔记本或备忘录,遇到类似情况可以复用些什么东西。这里就很像动态规划里的Memo
代码实现
#include <bits/stdc++.h>
using namespace std;
class KMP {
private:
vector<vector<int>> dp;
string pat;
public:
KMP(string pat) {
this->pat = pat;
int sizePat = pat.size();
dp.resize(sizePat, vector<int>(256, 0));
int X = 0;
dp[0][pat[0]] = 1;
for(int j = 1; j < sizePat; j++) {
for(int c = 0; c < 256; c++) {
dp[j][c] = dp[X][c];
}
dp[j][pat[j]] = j + 1;
X = dp[X][pat[j]];
}
}
int Match(string txt) {
int sizeTxt = txt.size(), sizePat = pat.size();
int j = 0;
for(int i = 0; i < sizeTxt; i++) {
j = dp[j][txt[i]];
if(j == sizePat) return i - sizePat + 1;
}
return -1;
}
};
int main() {
string pat, txt;
cin >> txt >> pat;
int index = KMP(pat).Match(txt);
cout << index;
return 0;
}