模式匹配(模范匹配):
子串在主串中的定位称为模式匹配或串匹配(字符串匹配) 。
方法一 Brute-Force模式匹配算法(暴力匹配法)
- 算法思想
s:目标串,sub:模式串
s=“s0 s1 s2…sn-1”
sub=“t0 t1 t2 …tm-1”
从主串s的第i个字符开始与子串sub的第一个字符比较,如果相等则继续比较后续字符;否则从主串s的下一字符开始与子串sub的的第一个字符比较,依次类推。
匹配成功返回sub中第一个字符在主串s中的位置,否则返回-1.
*主串总是从已匹配子串第一个字符的下一个字符开始(i-j+1),子串从第一个字符开始,两者进行比较,即S[i-j+1]和sub[0]比较。
2. 代码
//从S的第pos位置开始查找子串T;
class Solution{
public:
char *strStr(const char *S,int pos,const char* T){
if(!*T)
return (char*)S;
int i,j; //i:主串字符下标,j:子串字符下标
i=pos-1; //pos:第几个字符,从1开始;i:下标,从0开始
j=0;
while(i<strlen(S)&&j<strlen(T))
{
if(S[i]==T[j])
{i++;j++}
else
{i=i-j+1;j=0}
}
if(j>=strlen(T))
return i-(j-1);
else
return -1;
}
}
方法二 KMP算法
2.KMP算法思想
主串指针不回退,只增长。子串指针尽量后移,而不是像方法一中的总是从第一个元素开始。
在字符串s中寻找sub,当匹配到位置i时两个字符串不相等,这时我们需要将字符串T向前移动。常规方法是每次向前移动一位,但是它没有考虑前i-1位已经比较过这个事实,所以效率不高。事实上,如果我们提前计算某些信息,就有可能一次前移多位。假设i之前的字符串(T的子串)为f
f有如下特点:
A段字符串是f的一个前缀。
B段字符串是f的一个后缀。
A段字符串和B段字符串相等且长度为L。
所以前移k位之后,可以继续比较位置i的前提是T的前i-1个位置满足:长度为L=i-k-1的前缀A和后缀B相同。只有这样,我们才可以前移k位后从新的位置继续比较。前移只是一种形象的说法,在实际比较过程中是在T的最大公共长度之后进行再次比较。
next 思想见博客:http://blog.youkuaiyun.com/yutianzuijin/article/details/11954939
3.next函数
当主串i位置和子串j位置字符不匹配时,j要进行跳转,也就是指示子串比较位置的j要发生变化.
在求得了next[j]值之后,KMP算法的思想是:
设目标串(主串)为s,模式串为t ,并设i指针和j指针分别指示目标串和模式串中正待比较的字符,设i和j的初值均为1。若有si=tj,则i和j分别加1。否则,i不变,j退回到j=next[j]的位置,再比较si和tj,若相等,则i和j分别加1。否则,i不变,j再次退回到j=next[j]的位置,依此类推。直到下列两种可能:
(1) j退回到某个下一个[j]值时字符比较相等,则指针各自加1继续进行匹配。
(2)退回到j=-1,将i和j分别加1,即从主串的下一个字符s[i+1]模式串的t[0]重新开始匹配。
KMP算法如下:
//用KMP算法进行模式匹配,匹配返回位置,否则返回-1
//用静态方式保存字符串,s和t分别表示主串和模式串
#define Max_Strlen 1024
int next[Max_Strlen];
int KMP_index (const char* S ,const char *T)
{
int i=0 , j=0 ; /*初始匹配位置设置 */
while( (i<strlen(S))&&(j<strlen(T)))
{
if ((j==-1)|| (S[i]==T[j]))
{ k++ ; j++ ; }
else
j=next[j] ;
}
if (j>= strlen(T))
return(k-t.length) ;
else
return(-1) ;
}
求next函数:用归纳法:
模式串的next[j]值只与模式串sub有关,由next函数定义可知:
(1)当j=1时,next[1]=0; 当j=0,next[0]=-1;
(2)设next[j]=k,即在模式串中存在:
t[1…k-1]=t[j-(k- 1)… j-1],其中k在1和j之间的某个最大值,
(3)此时求next[j+1]的值有两种可能:
a.该i处不匹配时i跳转到next[i]
(1)若位置i和位置next[i]处的两个字符相同(下标从零开始),则next[i+1]等于next[i]加1。即next[i+1]=next[i]+1=k+1;
(2)如果两个位置的字符不相同,我们可以将长度为next[i]的字符串继续分割,获得其最大公共长度next[next[i]],然后再和位置i的字符比较。这是因为长度为next[i]前缀和后缀都可以分割成上部的构造,如果位置next[next[i]]和位置i的字符相同,则next[i+1]就等于next[next[i]]加1。如果不相等,就可以继续分割长度为next[next[i]]的字符串,直到next[0]=-1为止。(递推法)
小结next求法:
next[0]=-1;
假设next[j]=k,则表示已找到j以前的最大重叠子串,sub[0]~sub[k-1];
当j和next[j]位置处的元素相等时:next[j+1]=next[j]+1=k+1;
当j和next[j]位置处的元素不相等时:next[j+1]=next[…next[next[j]]]+1;一定要找到一个位置next[…next[next[j]]],此位置的元素与j位置处元素相同,且此位置之前的元素与j位置之前的部分元素能够匹配上。
/* 求模式串T的next函数值并保存在next数组中 */
/*因为假设j+1位置处匹配失败了才求的next[j+1],之前的j+1是现在的j(有++操作),如果跳转到k后的位置与j处元素相等,则还是会在与主串匹配过程中失败,则还要继续跳转,也就是就next值,一直要找到最终那个不会再跳转的位置*/
void next(const char *T ,int next[] )
{
int j=0 ,k=-1;
next[0]=-1;
while (j<strlen(T))
{
if ((k==-1)|| (T[k]==T[j]))
{
k++ ;
j++ ;
if ( T[k]!=T[j] )
next[j]=k;
else
next[j]=next[k];
}
else k=next[k] ;
}
}