之前学习了KMP算法,现在学习一下它的弱化版:MP算法。
为啥还要学习它呢?因为它是接下来要学习的AC-自动机的基础。
输入:主串S,子串T
输出:主串中子串第一次出现的位置(0-length(S-1))。匹配不到不输出.
样例:
S:ababcabcacbab
T:abcac
运行结果:
算法思想:
假定在匹配的过程中正在比较文本串*位置的字符和模板串abbaaba的最后一个字符。发现二者不同(称为失配),这时,朴素算法会把模板串后移一位,重新比较abbaaba的第一个字符和文本串!!位置的字符。
MP算法认为,既然!!位置已经比较过一次,就不应该再比一次了,事实上,我们已经知道灰色部分就是abbaab,应该可以直接利用模板串本身的特性判断出右移一位一定不是匹配。同理,右移两位或者三位也不行,但是右移四位是有可能的。这个时候,需要比较*处的字符刚和abbaaba的第三个字符。
下面其中编号为i的结点表示已经匹配了i个字符,匹配开始时当前状态是0,成功匹配时状态加1(表示多匹配一个字符)
而失配时沿着失配边走。用失配函数f[i]表示状态i失配时应转移到新状态,要特别注意的是f[0]=0.
void find(char *T, char *P, int *f)
{
int n, m, i, j;
n = strlen(T);
m = strlen(P);
getFail(P, f);
j = 0; //当前结点编号,初始为0号结点
for(i = 0; i < n; i++) //文本串当前指针
{
while(j && P[j] != T[i]) //顺着失配边走,直到可以匹配
j = f[j];
if(P[j] == T[i])
j++;
if(j == m)
printf("%d\n", i-m+1); //找到了
}
}
算法分析:
每次j++的时候会伴随一次i++,而每次j=f[j]的时候j至少会减1.最坏情况下j增加了n次,因此j=f[j]的次数不会超过n。
因此总时间复杂度O(n)。
状态转移图的构造是MP算法的关键。也是它最巧妙的地方。
算法的思想是:用自己匹配自己。根据f[0],f[1],f[i-1]递推f[i]。
//状态转移图,用自己匹配自己,用f[0],f[1]..f[i-1] 递推f[i].
void getFail(char *P, int *f)
{
int m, i, j;
f[0] = 0;
f[1] = 0; //递推边界初值
m = strlen(P);
for(i = 1; i < m; i++)
{
j = f[i];
while(j && P[i] != P[j])
j = f[j];
f[i+1] = (P[i] == P[j]) ? (j+1):0;
}
}