描述:给定一个正在编辑的文本S以及一个模式字符串P,判断P是否在S中出现,并返回P在S中的位置。
思路一:朴素算法,分别在S和P中设置指针指向第一个字符,若当前字符相同,则迭代比较后面字符;若不匹配,则令S的工作指针前移一位,指向P的指针从头迭代字符是否匹配。如图:
令当前cur指向A,依次迭代比较S,P后续元素。
找到B,C不匹配后,令当前cur后移,P从头迭代比较如图:
B,A不匹配,令cur后移,P从头迭代比较如图:
判断是否比较结束模式P最后一个元素,若是,则返回当前cur为模式P在S出现的位置。
class Solution{
public:
const char * isMatched(const char *str,const char *pa)
{
const char *cur=str;
const char *re=str;
const char *p=pa;
while(*(++p))//p向前移动pa长度M次
{
re++;//re向前移动M-1次,令re指向当前与pa匹配的字符段最后一个元素
}
if(*re=='\0')return NULL;
while(*re)
{
p=pa;
while(*cur==*p)
{
cur++;
p++;
}
if(*p=='\0')return cur;
else
{
cur++;
re++;
}
}
return NULL;
}
};
思路二:KMP算法
KMP算法是对朴素算法的改进,将时间从平方级降低到线性。KMP算法需要用到辅助数组next[M],长度与模式P一致。数组next求解如下:
当模式P为 A B A C
next[0]为模式P只有前一个元素的子串{A}的前缀与后缀的最长公共长度,为0;
next[1]为模式P有前两个元素的子串{A,B}的前缀{A}与后缀{B}的最长公共长度,为0;
next[2]为模式P有前三个元素的子串{A,B,A}的前缀{A,AB}与后缀{A,BA}的最长公共长度,为1;
next[3]为模式P有前四个元素子串{A,B,A,C}的前缀{A,AB,ABA}与后缀{C,AB,BAC}的最长公共长度,为0;
因此next数组为0 0 1 0
为什么要计算每一个子串前后缀的最长公共长度呢?这是因为朴素算法中,当出现不匹配的情况时,我们只将cur前移一位。但是实际上当前位不匹配时,之前的元素都能够和模式P对应,因此我们向前移动的位数不再为1,而是移动位数 = 已匹配的字符数 - next[i],i为最后一个匹配的字符在模式P中的位置。
A B A B A C
A B A C
此时,B,C不对应,但是前面元素均一一匹配,A为最后一个匹配字符next[2]=1,移动位数 = 已匹配的字符数 - next[i]=3-1=2。因此将向前移动两位
A B A B A C
A B A C
next求解思路:1.当子串只有一个元素时,next[i]=0;
2.当next[i-1]==0时,我们只需要比较最后一个元素与第一个元素是否相等,如next[1]=0,我们比较最后一个元素A与第一个元素A,相等则next[2]=1.
3.依次类推,当next[i-1]==j时,我们比较最后一个元素与第(j+1)个元素是否相等,如next[2]=1,此时我们比较最后一个元素C与第二个元素B,不相等,则比较最后一个元素与第一个元素A,不相等,则next[3]=0;,若最后一个元素为B,则next[3]=j+1=1+1=2。
void computeNext(const char *pa,int next[])
{
next[0]=0;
int L=strlen(pa);
int i=1;//字符串下标从1开始迭代
int pre=0;//前一位元素next[]最长前后缀长度
for(i,pre;i<L;i++)//遍历整个模式
{
while(pre>0&&pa[i]!=pa[pre])//寻找能够与当前i匹配上的pre位置
pre=next[pre-1];
if(pa[i]==pa[pre])pre++;
next[i]=pre;
}
}
KMP代码如下:
class Solution{
public:
const char* kmp(const char*str,const char*pa)
{
const char* cur=str;
const char* re=cur;
const char* p=pa;
while(*(++p))
re++;
int i;//已经匹配的长度
int j;//下次移动的步长
int idx=0;//从第一个字符开始匹配
while(*re)
{
i=next[idx];
p=pa+i;;
cur=cur+i;
while(*cur==*p)
{
cur++;
p++;
i++;
}
if(*p=='\0')return cur;
else
{
idx=i;
j=i-next[idx];
cur=cur+j;
re=re+j;
}
}
return NULL;
}
private:
void computeNext(const char pa[],int next[])
{
next[0]=0;
int L=strlen(pa);
int i=1;//字符串下标从1开始迭代
int pre=0;//前一位元素next[]最长前后缀长度
for(i,pre;i<L;i++)//遍历整个模式
{
while(pre>0&&pa[i]!=pa[pre])
pre=next[pre-1];
if(pa[i]==pa[pre])pre++;
next[i]=pre;
}
}
};