写在前面:
本文仅为作者个人学习记录,详细具体内容参考自知乎大佬labuladong 点击与大佬击剑
话不多说,上代码
public class KMP {
private int[][] dp;
private String pat;
public KMP(String pat) {
this.pat = pat;
int M = pat.length();
// dp定义为:dp[状态][字符] = 下个状态
//在相应的状态、字符定位位置的dp,其值指代下一个达到的状态
dp = new int[M][256];
// 初始化:
//只有在状态0处,遇到第一个字符时,dp才会为1进入状态1
dp[0][pat.charAt(0)] = 1;
// X 初始为状态 0
int X = 0;
// 构建状态转移图
for (int j = 1; j < M; j++) {
for (int c = 0; c < 256; c++)
dp[j][c] = dp[X][c];
//此处j从1开始,找到状态1想要进入状态2时遇到的字符
dp[j][pat.charAt(j)] = j + 1;
// 更新状态X
X = dp[X][pat.charAt(j)];
}
}
public int search(String txt) {
int M = pat.length();
int N = txt.length();
// pat 的初始状态为 0
int j = 0;
for (int i = 0; i < N; i++) {
// 计算 pat 的下一个状态
j = dp[j][txt.charAt(i)];
// 到达终止态,返回结果
if (j == M) return i - M + 1;
}
// 没到达终止态,匹配失败
return -1;
}
}
命名来自算法发表团队成员姓名,简写为KPM。
KPM算法与相对于简单匹配算法相比较,更为高效的串的模式匹配算法。
相比于简单匹配算法一步一匹配,一错全回退的不足之处,KPM算法优化了匹配模式串的效率。
本文介绍的KPM不同于课本所讲的,采用二维数组的方式来进行优化。
(不懂简单匹配算法的,还不速速去学~)
首先定义一下,方便理解:
pat——模式串,即需要从母串中匹配出的子串,长度M
txt——文本串,被匹配的母串,长度N
所谓KMP算法,即区别于简单模式匹配算法,永不退回TXT指针i,不走回头路。
将母串匹配子串的过程,理解为状态的转换
举个例子:
我们这里有一个pat“ABABC”,它的匹配过程是:先找到“A”,找到“A”后查看下一个字符是否是“B”,再看下一个……一直到从母串中匹配出“ABABC”为止。
这个匹配过程,将其理解为状态的转换:没有匹配到pat的任何一个字符叫做状态0,匹配到一个叫状态1,以此类推,即此处举例的字符串,在状态5时完成匹配。
理解了什么是状态的转换,那么匹配的过程就可以生动的描述为,状态的转换,从状态0一步一步转换到最终态 pat.length()
这里来定义一个二维数组dp——
dp = int[状态值][字符];
此处的二维数组中,行值代表当前的状态值,列值代表所匹配到的字符,而被_该行列值所指定的数组值_,即下一个要达到的状态值*。
到了这里,只需要找到dp = 1 - 5的几组转移关系的状态值和字符了,而dp也就成为了一副状态转移图。
那么其实匹配过程的函数就可以写出来了:
public int search(String txt){
int M = pat.length();
int N = txt.length();
//初始状态下,pat状态为0
int j = 0;
//遍历txt串
for(int i = 0; i < N; i++){
//状态j,遇到字符txt[i]
//若状态j时,遇到字符为匹配字符,则状态推进,即j+1
j = dp[j][txt.charAt(i)];
//判断一下是否达到终止态,达到终止态则返回匹配开头的索引
if(j == M) return i - M + 1;
}
//循环结束,遍历txt没达到终止态,则匹配失败
return -1;
}
上述过程理解之后,显而易见的是,需要根据dp数组的信息,在遍历txt时,来进行匹配,确定在状态几时遇到字符c,需要转换到状态几。
再看一次dp的定义:
dp = int[状态值][字符];
显然,dp的状态值与字符不成问题,问题是二者定位得到的数组值
AB ABA ABABC(2) ABABC(3)
AB ABC ABABC(2) ABABC(0)
ABAB ABABA ABABC(4) ABABC(3)
此处的例子中,虽然状态4没有匹配到想要的字符“C”,但匹配到字符“A”时,算法将其退回至状态3,这里又是什么逻辑呢?
上述例子中,最后一个为什么可以从状态4回退到状态3呢?细心的朋友可能发现在最开始的代码里,定义了一个状态X
int X = 0;
给其初始化值为0,那么这里的状态X,就是匹配状态永远晚一步的状态,何解?
X = dp[X][txt.charAt(0)]
dp[j][c] = dp[X][c];
由此,解释了为什么KMP算法不需要txt指针的回退,pat的不断回退代替了txt指针回退,不断与母串进行匹配比较
上述逻辑言明,即可得到dp数组中值究竟何来:
public KMP(String pat){
this.pat = pat;
int M = pat.length();
//dp的值为下一个状态
dp = new int[M][256];
//初始化:状态0时,遇到pat的第一个字符,状态推进到1
dp[0][pat.charAt(0)] = 1;
//状态X初始为0
int X = 0;
//当前状态j从1开始
for( int j = 1; j < M; j++){
for(int c = 0; c < 256; c++){
if(pat.charAt(j) == c)
dp[j][c] = j + 1;
else
dp[j][c] = dp[X][c];
}
//更新X状态
X = dp[X][pat.charAt(j)];
}
至此,二维数组实现KMP算法所有逻辑及代码梳理结束
记录学习、爬坑经验
究极小白,欢迎大佬指点!
希望可以帮到你!