KMP思路:
这个例子中模式串bababb在原串中第13个字符开始的地方出现
我们假设仍然使用最普通的方法来进行判断,即我们先枚举原串中的一个起始位置,然后判断从这个位置开始的字符串是否能和模式串进行完匹配。然后我们来看这个过程中有没有什么可以缩减的计算量。
在起始点为1的时候,匹配到第6个字符的时候发生了失败,这个时候我们应当做的是将模式串右移一位,然后从头开始判断。
发现第一位就发现不能进行匹配
然后再将模式串右移一位,再从头开始判断,这次成功的越过了原串的第7个字符,在第8个字符产生了不同。
都是要么最后一个字符匹配不成功,要么就是第一个字符就匹配不成功,一直到了最后一次的时候才匹配成功
这是第一次产生了字符不匹配的情况,那么接下来的过程中一定会出现两种情况之一:一种情况是模式串与原串的对齐点(即枚举的原串中的起点位置)越过了这条线,仍然没能匹配成功,而另一种情况是原串中这个位置的字符与模式串中某个位置的字符匹配。
如果用i表示原串和模式串产生分歧的位置(模式串上的位置,注意!这个和对齐点是不一样的东西,一个在原串上,一个在模式串上),用j表示为了匹配掉位置i上产生分歧的字符而将模式串的对齐点移动到的位置,则有,模式串[1, i-j]的这一段和[j, i - 1]这一段是相同的。比如在这个例子中i=6,j=3,模式串[1, 3]和[3,5]是相同的。
只有在存在一个长度k,使得模式串[1, i-k]和[k, i-1]这两段相同的情况下,将模式串对其到位置k,才能保证原串和模式串的匹配过程能够进入到原串的位置i是否和模式串的对应字符相同的判定,在别的情况下,根本都进入不到位置i的判断就会发生不一致的情况了。
需要的一个数据是,这个长度k最长是多少,而且我们对于模式串的每一个位置i,都要计算这个值,即next数组。
NEXT数组的使用
NEXT[0] = -1
NEXT[i] = max{ 0<=k< i | str.substring(1, k) == str.substring(i - k +1 , i) } 其中str.substring(i, j)表示str从位置i到位置j的子串,如果i>j则,substring为空。对于上述例子
模式串: b a b a b b
NEXT: 0 0 1 2 3 1
为了表明NEXT的所有使用情况,换一个原串。首先,我们第一次匹配,如果用ori表示原串,用par表示模式串,用p表示原串的下标(从1开始),用q表示模式串的下标(从1开始)的话,会发现最多匹配到p=5, q=5就不能往下匹配了,因为此时ori[p +1]不等于par[q + 1]
此时,令q = NEXT[q],并将ori[1…p]和par[1…q]对齐,便会发现ori[1…p]和par[1…q]仍然是一一对应的。
此时,ori[p+1]和par[q+1]相同了,于是可以继续往下匹配,但是到了p=7,q=5的时候又发现不能够接着匹配了。
此时,令q = NEXT[q],并将ori[1…p]和par[1…q]对齐,便会发现ori[1…p]和par[1…q]仍然是一一对应的。
此时,ori[p+1]和par[q+1]仍然不相同,于是还得令q=NEXT[q]。
此时,ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。
此时,ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。
到了这一步,就相当于之前所说的模式串与原串的对齐点(即枚举的原串中的起点位置)越过了这条线(当时指C右侧的那条线)的情况,这种情况下,就应当p和q均+1,然后继续之前的操作。
NEXT数组的计算
首先不想如何求整个NEXT数组,而是假设已经知道了之前例子中模式串的NEXT[1…4],来求NEXT[5]
把par.substring(1, 5)当做新的原串ori_new,然后把par.substring(1, 4)当做新的模式串par。
首先就直接匹配到了p=4, q=4的情况,这时候严格来说已经算匹配完成了,但是肯定不是就这么结束的,此时par_new[q +1]因为是空字符,所以肯定和ori_new[p+1]匹配不上。于是令q = NEXT[q]
这时候ori_new[p + 1]就直接和par_new[q + 1]匹配上了,于是新的p=5,q=3,最后的q就是NEXT[5]
继续计算NEXT[6]
首先没有必要重新从头开始匹配,直接在原串和模式串的后面加上第6个字符就可以了。
没法继续匹配,于是令q=NEXT[q]。
还是没法继续匹配,于是令q=NEXT[q]。
此时可以匹配了,新的p=6,q=1,所以NEXT[6]就是1,NEXT数组的本身可以用这种递归的方式进行求解
#include<iostream>
#include<string>
using namespace std;
class KMP
{
private:
string m_ori;//主串
string m_par;//模式串
int* m_next;//next数组
int location;//模式串在主串中第一次出现的位置
int sum;//模式串在主串中的出现次数
public:
KMP()
{
m_ori = '0';
m_par = '0';
m_next = NULL;
location = 0;
sum = 0;
}
void Init(string ori, string par);//输入主串和模式串,以及next数组的初始化
void Get_Next();//求出next数组的各个元素值
void First_Location();//找出模式串在主串中第一次出现的位置
void Number_Of_Occurrences();//计算模式串在主串中的出现次数
void Par_Match();//模式串匹配
};
void KMP::Init(string ori, string par)
{
m_ori = ori;
m_par = par;
int n = m_par.length();
m_next = new int[n + 1];
m_next[0] = -1;
for (int i = 1; i <= n; i++)
m_next[i] = 0;
}
void KMP::Get_Next()
{
int n = m_par.length();
int q = 0;
for (int i = 1;i < n;i++)
{
q = i;
while (m_par[m_next[q]] != m_par[i])
{
q = m_next[q];
if (m_next[q] == -1)
break;
}
q = m_next[q];
m_next[i + 1] = q + 1;
}
}
void KMP::First_Location()
{
int n_ori = m_ori.length();
int n_par = m_par.length();
int flag = 1, q = 0, i = 0;
for (i = 0;i < n_ori;i++)
{
while (m_ori[i] != m_par[q])
{
if (m_next[q] == -1)
i++;
q = m_next[q] + 1;
}
if (q == n_par - 1)
break;
q++;
}
location = i + 1 - n_par;
}
void KMP::Number_Of_Occurrences()
{
int n_ori = m_ori.length();
int n_par = m_par.length();
int flag = 1, q = 0, i = 0;
for (i = 0;i < n_ori;i++)
{
while (m_ori[i] != m_par[q])
{
if (m_next[q] == -1)
i++;
q = m_next[q] + 1;
}
if (q == n_par - 1)
{
sum++;
q = -1;
}
q++;
}
}
void KMP::Par_Match()
{
this->Get_Next();
this->First_Location();
this->Number_Of_Occurrences();
cout << "模式串在主串中第一次出现的位置:" << location + 1 << endl;
cout << "模式串在主串中的出现次数:" << sum << endl;
}
int main()
{
KMP k;
string ori;//主串
string par;//模式串
cout << "按顺序输入主串、模式串:";
cin >> ori >> par;
k.Init(ori, par);
k.Par_Match();
return 0;
}