串的模式匹配-EMP算法

串的模式匹配-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),继续与模式串的第一个字符开始比较(即 j0 的位置),直到比较完成;

看似上面的代码没有任何问题,似乎很完美,假设主串的长度为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
*/

后记:也许我写的比较模糊,真的尽力了

posted @ 2019-10-20 13:28  翁德彪  阅读(723)  评论(0编辑  收藏  举报