目的:判断目标串(T串)中是否含有模式串(P串),在下文中为了方便,命名目标串为“长串”,模式串为“短串”。
显而易见的是,不断判断字符是否相同的过程就是两个指针移动的过程。MP算法就是要相比于暴力搜索BF,简化指针移动的过程。
首先对图中的标识做解释,黄色填充代表长串指针的移动,红色字符代表匹配失效的时刻,绿色填充的数字代表列,也是长串每一个字符的索引值,蓝色填充数字代表短串的索引。
MP算法过程:长串的指针从来没有回溯。长串指针移动规则:长串在与短串的第一个字符匹配失败时指针向前移动一个位置,长串在短串某个字符匹配成功时向前移动一个位置,长串在与短串(除第一个字符)的某字符匹配失败时不移动指针。短串指针移动规则:若在短串的j位置匹配失败,下一次的匹配位置移动到f(j-1)+1 的位置。
好了,按照这个规则我们用按图索骥走一遍吧。第一次匹配失败发生在第1列(绿色列),并不是短串的第一个字符失匹,长串指针不动,短串指针j=1(蓝色索引),下一次(f(1-1)+1)=0,指针到0索引,也就是第一个字符。第二次不匹配发生在短串的第一个字符,长串指针移动一个,短串指针仍在第一个字符。第三次不匹配发生在第八列,因为不是短串的首字符,长串指针不动,短串指针j=6,f(6-1)+1=2,指针移动到索引为2的字符。第四次不匹配,失匹字符不是短串第一个,所以长串指针不动,短串指针j=2,f(2-1)+1=0,指针移动到索引0字符。最后一直在匹配,长串指针向前走。 MP好处:长串没有回溯。从第三次不匹配到图中第四次,短串走了很远。肯定要比暴力一次走一步搜索速度快。至于为什么,就是因为短串中前部分子串和后部分子串有可能相同,这就导致移动的时候可以多移动点,用文字我解释不清楚,大家再想想吧。
void MP(string T, string P){
int lenT = T.length();//长
int lenP = P.length();//短
vector<int> v(lenP + 1, 0);
MP_bulid_v(v, P);
int i = 0, j = 0;//i是短串的指针 j是长串的指针
//整个过程就是两个指针的移动
while (j < lenT){
while (i>-1 && P[i] != T[j])
i = v[i];
i++;
j++;
if (i >= lenP){
//成功找到一组
cout << j - i;
i = v[i];//接下来找下一组
}
}
}
void MP_bulid_v(vector<int> &v, string &P){
int m = P.length();
int i = 0;
int j = v[0] = -1;
while (i < m){
while (j>-1 && P[i] != P[j])
j = v[j];//这里的实现非常巧妙 很难想到
i++;
j++;
v[i] = j;
}
}