KMP算法用于字符串匹配中,在看了b站大佬的解释之后略有理解,在这里归纳以下以帮助记忆:
大佬视频:https://www.bilibili.com/video/av11866460?from=search&seid=3835441040523799289
1.暴力匹配
对于两个字符串:
A:a b c a b d a a b b c e
P: b d a a b
我们通常暴力解法是对A与P进行遍历比较,如果
if(A[i]==P[j])
i++;
j++;
if(A[i] != P[j])
i=i-j+1;
j=0;
直到P中所有字符都遍历比较成功,则表示完成;
算法如下
bool Match(string A, string P)
{
if (A.empty() || P.empty())
return false;
int i = 0, j = 0;
while (i < A.size())
{
if (A[i] == P[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
if (j == P.size())
return true;
}
return false;
}
暴力算法比较容易理解,但是在每次匹配错误的情况下i,j都要退回最前面位置重新进行匹配,在某些情况下操作就会出现没必要的浪费;
比如A:aaaaaaaab;
P:aaab;
很显然在前几次匹配过程中,在最后b位置匹配失败,那么就要重复匹配前面的aaa,可是我们很明显的看到没有必要重复比较,再P[3]!=A[3]时,只要如果再从头让P[0]和A[1]进行比较就会降低效率,所以这里提出了KMP算法;
KMP算法
为了解决在匹配过程中的重复匹配问题,我们需要知道如果匹配失败的情况下,我们应该返回什么位置才能避免重复匹配。
对于以上的字符串P,我们先获得它的prefix table
将它分解
a 显然它没有重复部分,为0,即当在P[1]上匹配失败时,就需要对第0位重新匹配了
aa 它的前置和后置相同,都为a,其数为1,即当在P[2]上匹配失败时,则可以跳过对前面第一位的匹配了
aaa 它的前置和后置相同,为aa,就是有两个相同,前置值为a2,那么在P[3]上匹配失败时,可以跳过对前两位的重新匹配
aaab 不用管它,完整的字符串不用管它,因为当在最后一位不匹配的时候只需要判断前面是否需要重新匹配即可
在这里我们获得它的前置表(prefix table),第一位置为-1,以后的位分别取以上获得的前置数值;
pt= [-1, 0,1, 2]
P= [ a, a, a, b]
对应位 0, 1, 2, 3
A=[a,a,a,a,a,a,a,b]
我们开始对它进行匹配:
在j=3,i=3的时候,我们可以看到不匹配了,则在前置表里面pt[3]=2,意味着最前面2位与P[3]之前的最后面2位是相同的,可以直接移过来进行匹配,不需要重复匹配了,于是从重复匹配的后一位P[2]开始和当前A[3]比较,即此时i=3,j=2;显然我们不需要从i=1,j=0从头开始匹配了,这样提高了效率;
获得prefit table算法
vector<int> prefitTable(string P)
{
vector<int> pt(P.size(), -1);
for (int i = 1; i < P.size(); i++)
{
int count=0;
int temp = i - 1;
for (int j = 1; j <=temp; j++)
{
if (P[temp - j] == P[i - j])
count++;
else
{
count = 0;
temp--;
j = 0;
}
}
pt[i] = count;
}
return pt;
}
KMP算法
bool KMP(string A, string P)
{
vector<int> pt = prefitTable(P);
if (A.empty() || P.empty())
return false;
int i = 0, j = 0;
while (i < A.size())
{
if (A[i] == P[j])
{
i++;
j++;
}
else
{
//注意这边的判断,是KMP的重要内容
if(j!=0)
j = pt[j];
else
i++;
}
if (j == P.size())
return true;
}
return false;
}