具体理论,请看《大话数据结构》、《数据结构(C语言版)》严蔚敏。
此文为此算法的补充说明。
1.直接的子串搜索算法
//函数功能:返回子串T在主串S中第一次被发现的位置
//返回值:如果被发现 返回子串首字母在S中的位置 否则返回-1
//注意:子串T和S从索引1开始
int FindFirstPosition(const std::string & S, const std::string & T)
{
int sLen = S.size()-1;
int tLen = T.size()-1;
int i=1; //0
int j=1; //0
while(i<=sLen)
{
if(j>tLen)
return i-j+1;
if(S[i] == T[j])
{
i++;
j++;
}
else
{
i = i-j+2; //i退回到上次匹配首位的下一位
j = 1; //j退回到子串T的首位
}
}
return -1;
}
难点主要为这句代码
i = i-j+2; //i退回到上次匹配首位的下一位
看下图
if(S[i] == T[j])之后,第一次S[i] != T[j]时,主串i偏移值为黄色长条长度,子串j偏移值为蓝色长条长度。
子串要再与主串比较,子串的偏移值j自然为0,主串的i则位于橙色格子处。
则橙色格子处的偏移值=黄色长条长度-蓝色长条长度+1=i-j+2=7-3+2=6
2.优化
看如下例子
可归纳出一规则:如果子串中没有重复项,下一次比较位置为按上次刚开始比较位置+子串长度+1。
再使用符号语言描述:设主串为S={A1,A2,...,An},子串为T={a1,a2,...am},主串的比较位置为i,子串的比较为j,
如果T中没有重复项,则匹配失败后,j=i-j+T.length+1
例如上图第一次比较时,i=2,j=2,下一次比较位置 pos=2-2+4+1=5。再下一次比较位置pos=6-2+4+1=9。如下图所示。
如果T中有重复项,应该如何处理?
看此具体例子
当第一次比较时主串的子串A1A2A3A4A5==子串的子串a1a2a3a4a5,下一次比较时,子串的比较位置j=3。
j=粉色子串长度+1。粉色子串的最大长度=子串的最大相似度。
子串的最大相似度定义:子串为T={a1,a2,...am}
A1, am
A1,a2, ||am-1,am
A1,a2,a3, ||am-2,am-1,am
A1,a2,...ak, //am-k+1,am-k,...am-k-1,...am
j详细公式【2】P81 4-5公式
使用【2】P81 4-5公式求next数组
void CalcNextArrayPrimarily(const std::string & pattern, std::vector<int> & next)
{
int pLen = pattern.size();
//字?符?串?索÷引皔从洙?开a始? pattern[0]为a点?位?符? 故ê长¤度è减?1
pLen--;
if(pLen>0)//当獭纉=1时骸?next[j]=0;
next[1] = 0;
for(int j=2; j<=pLen; j++)
{
for(int k=2; k<j;k++)
{
std::string::const_iterator ite = pattern.begin()+1; //索÷引皔从洙?开a始?
std::string pFront(ite, ite+k-1);
int t = j-k;
std::string pBack(ite+t, ite+j-1);
if(pFront == pBack)
{
if(k>next[j])
next[j] = k;
}
}
}
}
3.经典算法
//T[0] 保存字符串的长度
void GetNextArray(char T[], int next[])
{
//求模式串T的next函数值并存入数组next
int i=1;
next[1]=0;
int j = 0;
while(i<T[0])
{
if(j==0 || T[i]==T[j])
{
i++;
j++;
next[i] = j;
}
else
j = next[j];
}
}
设子串T1=a1...a(k-1)与子串T2=aj-k+1...aj-1, j为当前模式串长度。若a(k-1)=a(j-1),next(j)=next(j)+1,这很好
理解。若a(k-1)!=a(j-1)时,如何处理?看下图
参考文献
【1】《大话数据结构》
【2】《数据结构(C语言版)》严蔚敏。
附:完整源码
#include <string>
#include <iostream>
#include <vector>
//函数功能:返回子串T在主串S中第一次被发现的位置
//返回值:如果被发现 返回子串首字母在S中的位置 否则返回-1
//注意:子串T和S从索引1开始
int FindFirstPosition(const std::string & S, const std::string & T)
{
int sLen = S.size()-1;
int tLen = T.size()-1;
int i=1; //0
int j=1; //0
while(i<=sLen)
{
if(j>tLen) //因为j递增了一次 所以j == tLen 不然则为j == tLen -1
return i-j+1;
if(S[i] == T[j])
{
i++;
j++;
}
else
{
i = i-j+2; //i退回到上次匹配首位的下一位
j = 1; //j退回到子串T的首位
}
}
return -1;
}
void CalcNextArrayPrimarily(const std::string & pattern, std::vector<int> & next)
{
int pLen = pattern.size();
//字符串索引从1开始 pattern[0]为点位符 故长度减1
pLen--;
if(pLen>0)//当j=1时 next[j]=0;
next[1] = 0;
for(int j=2; j<=pLen; j++)
{
for(int k=2; k<j;k++)
{
std::string::const_iterator ite = pattern.begin()+1; //索引从1开始
std::string pFront(ite, ite+k-1);
int t = j-k;
std::string pBack(ite+t, ite+j-1);
if(pFront == pBack)
{
if(k>next[j])
next[j] = k;
}
}
}
}
//T[0] 保存字符串的长度
void GetNextArray(char T[], int next[])
{
//求模式串T的next函数值并存入数组next
int i=1;
next[1]=0;
int j = 0;
while(i<T[0])
{
if(j==0 || T[i]==T[j])
{
i++;
j++;
next[i] = j;
}
else
j = next[j];
}
}
//函数功能:返回子串T在主串S中第一次被发现的位置
//返回值:如果被发现 返回子串首字母在S中的位置 否则返回-1
int KMP(const std::string & S, const std::string & T, int next[])
{
int sLen = S.size()-1;
int tLen = T.size()-1;
int i=1; //0
int j=1; //0
while(i<=sLen)
{
if(j>tLen) //因为j递增了一次 所以j == tLen 不然则为j == tLen -1
return i-j+1;
if(j==0 ||S[i] == T[j]) //j==0 因为j=next[j]时,next[j]可能为0
{
i++;
j++;
}
else
{
i = i-j+2; //i退回到上次匹配首位的下一位
//j = 1; //j退回到子串T的首位
j = next[j];
}
}
return -1;
}
void TestNextArray()
{
///
std::string S1("*kapibcumbcbfe");
std::string T1("*bc");
int index1 =FindFirstPosition(S1, T1);
std::cout<<index1<<std::endl;
std::string S2("*kapibcumbcbfe");
std::string T2("*bcb");
int index2 =FindFirstPosition(S2, T2);
std::cout<<index2<<std::endl;
///
std::string pattern("*abaabcac"); //索引从1开始
std::vector<int> next1(pattern.size(), 1);
CalcNextArrayPrimarily(pattern, next1);
std::string pattern2("*akakffakake"); //索引从1开始
std::vector<int> next2(pattern2.size(), 1);
CalcNextArrayPrimarily(pattern2, next2);
char T3[12]={ 11,'a', 'k', 's', 'k', 'f', 'f', 'a', 'k', 'a', 'k','e'};
int next3[12];
GetNextArray(T3, next3);
/
std::string S4("*kapibcumbcbfe");
std::string T4("*bc");
std::vector<int> next4(T4.size(), 1);
CalcNextArrayPrimarily(T4, next4);
std::vector<int> next5(T4.size(), 1);
char T4_1[3]={2,'b', 'c'};
GetNextArray(T4_1, next5.data());
int index4 =KMP(S4, T4, next4.data());
std::cout<<index4<<std::endl;
int index5 =KMP(S4, T4, next5.data());
std::cout<<index4<<std::endl;
std::string S6("*acabaabaabcacaabc");
std::string T6="*abaabcac";
T6[0]=(char)6;
char T6_1[9]={8,'a','b','a','a','b','c','a','c'};
std::vector<int> next6(T6.size(), 1);
GetNextArray(T6_1, next6.data());
int index6 =KMP(S6, T6, next6.data());
std::cout<<index6<<std::endl;
char T7_1[9]={8,'a','b','c','e','a','b','c','c'};
std::vector<int> next11(9, 1);
GetNextArray(T7_1, next11.data());
}