串的模式匹配-EMP算法
前言
算法的世界很奇妙,最近刚刚学到了严蔚敏的数据结构串的模式匹配;挣扎了两天,也许这是一个简单的问题,可能是我太笨了,哈哈,这一节来回看了好几遍,总算是悟透了一点点,于是就想把我所理解的写下来,如果有错误或不懂的地方,欢迎留言。
先来看传统算法
原理
以书中的字符串为例:
i-> 0 1 2 3 4 5 6 7 8....
主串: a c a b a a b a a b c a c a a b c
j-> 0 1 2 3 4 5
模式串: a b a a b c
定义s[i] 指向主串,p[j] 指向模式串;开始的时候,j=0 ,i=0; p[j] 与 s[i]比较,如果s[i]==p[j],则j、i指针后移,,p[j+1]与s[i+1]比较,直到模式串的所有字符与主串中的从s[i]至s[i+j]的字符都相等,如果出现不相等的情况,则i 回到主串的第二个位置(即 i ==1),继续与模式串的第一个字符开始比较(即 j==0 的位置),直到比较完成;
看似上面的代码没有任何问题,似乎很完美,假设主串的长度为n,模式串的长度为m,来看看下面例子的时间复杂度;
i-> 0 1 2 3 4 5 6 7 8....
主串: a b c d e f g h i j k l m n
j-> 0 1 2 3 4 5
模式串: i j k l m n
上述例子仅需比较n次即可,时间复杂度为O(n),可见效率还是很高的;再来看看下面的例子;
i-> 0 1 2 3 4 5 6 7 8....
主串: a a a a a a a a a a b
j-> 0 1 2 3 4 5
模式串: a a a a a b
上面事例中,每次比较的结果都是最后一个字符不匹配,当第一轮比较后,实际上i并不需要回溯到s[1],因为主串的前9个都是a,面对这种情况,传统算法就显得有些力不从心,由此出现了EMP改进算法;
代码
注:下面代码和书本有些区别,默认的是串的第一个空间不用来存放长度,即串是从0开始,而书上的串,第一个空间存放的长度
int Index(HString *T,HString *S, int pos)
{
int j=0;
while(pos<T->length&&j<S->length)
{
if(T->ch[pos]==S->ch[j])
{
pos++;j++;
}
else
{
pos=pos-j+1;
j=0;
}
}
if(j>=S->length)
{
int len;
len=pos-S->length+1;
printf("%d",len);
return len;
}
else
{
return 0;
}
}
EMP算法
原理
EMP算法的基本思想就是,主串指针i不需要回溯,通过移动模式串进行匹配,通过已经匹配好的子串计算移动位置;
以书中为例:
i-> 0 1 2 3 4 5 6 7 8....
主串: a b a b c a b c a c b a b
j-> 0 1 2 3 4
模式串: a b c a c
匹配过程如下:
第一轮:
i-> 0 1 i 3 4 5 6 7 8....
主串: a b a b c a b c a c b a b
j-> 0 1 j
模式串: a b c
解析:当匹配到第三个时,s[2]!=p[2],此时,i 不必移动,通过移动j到模式串的第一个,即s[2]和p[0]比较
第二轮:
i-> 0 1 2 3 4 5 i 7 8....
主串: a b a b c a b c a c b a b
j-> 0 1 2 3 j
模式串: a b c a c
解析:第二轮是从s[2]与p[0]开始匹配,当i移动到6,j移动到5时,有出现不匹配的情况,此时又需移动模式串,可是问题来了,j要移动到模式串的哪个位置呢,通过观察我们发现,s[i]==p[1]、s[5]==p[0],因此我们仅需将j移动到p[1]的位置,此时相当于已经排好了两个,再接着比较下一个即可;
第三轮:
i-> 0 1 i 3 4 5 6 7 8....
主串: a b a b c a b c a c b a b
j-> 0 1 2 3 4
模式串: a b c a c
第三轮即匹配完成
由上面的匹配过程可以看出,当发生不匹配的情况时,只需要移动j即可,因此我们可以用一个数组next[]来存储模式串中每个字符发生不匹配时,所对应需要移动到的位置,因此代码可以改进成如下的样子;
//T是注主串,S是模式串
int Index(HString *T,HString *S, int pos,int *next)
{
int j=0;
while(pos<T->length&&j<S->length)
{
if(j==-1||T->ch[pos]==S->ch[j])
{
pos++;j++;
}
else
{
j=*(next+j); //此处是与传统算法的主要区别
}
}
if(j>=S->length)
{
int len;
len=pos-S->length+1;
printf("%d\n",len);
return len;
}
else
{
return 0;
}
}
求next[] 的值
通过上面我们已经知道,想要让上面的代码正确运行,我们还需要求出next[j]的值;
例如:
j-> 0 1 2 3 4 5 6 7
模式串: a b a a b c a c
next[]:-1 0 0 1 1 2 0 1
- 当模式串的第一个就与主串s[i]不匹配时,此时应该i+1,即s[i+1]继续与模式串的第一个匹配,因此,我们令next[0]==-1;
- 接着有两种情况,第一种:如果当前s[j]和j所对应的next[j]所对应的字符串相等,即s[j]==s[next[j]],此时s[j+1]所对应的next[j+1]就等于next[j]+1;另外,s[0]所对应的next[j]==-1,而字符串是从下标0开始的,因此s[1]所对应的值一定是0,即next[]==0;
- 第二种:当如果当前s[j]和j所对应的next[j]所对应的字符串不相等,则j应该回到s[next[j]]所对应的next[]值继续行前比较;
求值过程:
第一个:
next[0]==-1;
第二个:
next[1]==0;
第三个:
由于s[1]!=s[next[0]],所以next[2]==0;
第四个:
因为s[2]==s[next[2]],所以,无论s[3]匹配还是不匹配,next[3]都等于next[2]+1,即s[3]=1;
第五个:
s[3]!=s[next[3]],然后接着向前比较,s[next[3]]==s[next[s[next[3]]]],所以此时,s[4]==next[s[next[3]]]+1;
第六个:
同样,s[4]!=s[next[4]],所以,s[5]==next[4]+1;
后面几个也是同理
代码实现:
void Get_next(HString *T,int *next)
{
int i=0,j=-1;
*(next +0)=-1;
while(i<T->length)
{
if(j==-1||T->ch[i]==T->ch[j])
{
++j;++i;
*(next+i)=j;
}
else
{
j=*(next+j);
}
}
/* 测试数组next[]的值,是否正确
for(int t=0;t<T->length;t++)
{
printf("%d ",*(next+t));
}
*/
}
完整代码:
#include<stdio.h>
#include<stdio.h>
#include <stdlib.h>
typedef struct
{
char *ch;
int length;
}HString;
void StrAssign(HString *T,char *chars)
{
int len = 0;
while(*(chars+len)!='\0')
{
len++;
}
if(len==0)
{
T->ch=NULL;
T->length=0;
}
else
{
T->ch=(char *)malloc(len * sizeof(char));
for(int i=0;i<len;i++)
{
T->ch[i]=*(chars+i);
}
T->length=len;
}
// printf("%d\n",T->length);
}
void Get_next(HString *T,int *next)
{
int i=0,j=-1;
*(next +0)=-1;
while(i<T->length)
{
if(j==-1||T->ch[i]==T->ch[j])
{
++j;++i;
*(next+i)=j;
}
else
{
j=*(next+j);
}
}
/*
for(int t=0;t<T->length;t++)
{
printf("%d ",*(next+t));
}
*/
}
int Index(HString *T,HString *S, int pos,int *next)
{
int j=0;
while(pos<T->length&&j<S->length)
{
if(j==-1||T->ch[pos]==S->ch[j])//由于数组是从0开始,当j==-1时,直接运行
{
pos++;j++;
}
else
{
j=*(next+j);
}
}
if(j>=S->length)
{
int len;
len=pos-S->length+1;
printf("%d\n",len);
return len;
}
else
{
return 0;
}
}
int main()
{
int next[20];
char s1[100];
char s2[100];
printf(" 请输入字符串s1:\n");
gets(s1);
printf("请输入字符串s2:\n");
gets(s2);
HString S,S1,S2,*p,*p1,*p2;
p=&S; p1=&S1; p2=&S2;
StrAssign(p1, s1);
StrAssign(p2, s2);
Get_next(p2, next);
Index(p1, p2, 2, next);
}
/* 测试
请输入字符串s1:
warning: this program uses gets(), which is unsafe.
ababcabcacbab
请输入字符串s2:
abcac
6
Program ended with exit code: 0
*/
后记:也许我写的比较模糊,真的尽力了