首先介绍一些字符串的基础知识:
假定有字符串S S.size()==n
子串是指字符串的连续区间S[i,j] i,j<=n;
子序列是指将字符串中若干元素提取出来并不改变相对位置形成的序列
后缀 是指从某个位置 i开始到整个串末尾结束的一个特殊子串
前缀 是指从串首开始到某个位置 i结束的一个特殊子串
真前后缀即是指不包含S本身的字串
PMT表 即Partial Match Table,部分匹配表
它是真前缀与真后缀的集合之交集中,最长元素的长度
以下用PI数组代表PMT表
PI数组既是求当前位的最长公共前后缀
朴素的做法:
按照长度n对每个字串进行匹配
优化1:
相邻的前缀函数值至多增加1优化2:
失配后的跳转
求PMT表
求PI数组#1(此方法的PI数组需要处理 所有位向后移动一位) 这里实际上求得是PMT表
void pmt(vector<int>&pi,string &s) //递推求PMT
{
int n=s.size();//获取字符串长度
pi.resize(n);
for(int i=1;i<n;i++){
//PI[0]=0
int j=pi[i-1];//j的初始化 令j为前一位的最长公共前后缀
while(j>0&&s[i]!=s[j]) //j>0 为边界条件 如果一直失配
j=pi[j-1]; //寻找第二长的j
if(s[i]==s[j])j++; //如果匹配成功 则在前一位上++;
pi[i]=j; //更新当前位置上的最长公共前后缀
}
}
void nextval(vector<int>&pi)//处理PMT使其转换为nextval
{
pi.insert(pi.begin(),-1);//队首变为-1 插入后所有位后移一位
pi.pop_back(); //弹出末尾多余元素
}
#2直接求得PMT整体右移一位的结果 对应大多数文章中的NEXT数组
void prefix(string s, int* pi)
{
int i = 0;
int j = -1;
int len = s.length();
pi[0] = -1;
while (i < len)
{
if (j == -1 || s[i] == s[j])
pi[++i] = ++j;
else
j = pi[j];
}
}
MP算法
KMP算法
该算法由 Knuth、Pratt 和 Morris 在 1977 年共同发布
主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高
PI数组的预处理
为了后续使用KMP更加方便
我们将PI求出后 将PI[0]初始化为-1;
MP(核心笔者认为是PI的求解)
int kmp(vector<int> pi,string s,string t){
int n=s.size();//获取目标串和模式串的长度
int m=t.size();
int i=0,j=0;// two pointers
while(i<n&&j<m){
if(j==-1||s[i]==t[j])// 如果当前仅一个字母或目标串匹配
{
i++;
j++;//i j指针都向右移动
}
else{
j=pi[j];//否则将模式串整个向右移动
}
} if(j>=m) return (i-m);//如果找到了模式串
else return -1;//那么输出模式串第一次出现的位置 否则输出-1
}
int kmp(vector<int> pi,string s,string t){
int cnt=0;
int n=s.size();
int m=t.size();
int i=0,j=0;// two pointers
while(i<n){ //统计模式串的个数不用限制j
if(j==-1||s[i]==t[j])
{
i++;
j++;
}
else
j=pi[j];
if(j==m)
cnt++; //j==m说明匹配完成一次
}
return cnt;
}
KMP的K部分笔者还没弄明白
以上内容只能算MP 等弄明白K了 再补上
学习总结: