模式串匹配问题

问题描述:

给出模式串P和文本串T

有以下问题:1、模式串是否出现在文本串。2、文本串中出现模式串的所有位置。3、模式串在文本串出现的次数。(暂时想不出那么多)


对于上述的每个问题都可以逐个字符比较来找出答案。代码如下

    for(int i=0;i<len(T)-len(S);i++)
    {
        int j=0;
        while(j<len(S))
        {
            if(T[i+j]==P[j])j++;
            else break;
        }
        if(j==len(S))
        {
            //对应操作
        }
    }
n=len(T),m=len(S)。

显然上述算法的时间复杂度是O(n*m)。

对于k个模式串问题时,时间复杂度就是O(n*m*k)。


对于一个模式串的匹配问题,可以使用KMP算法。

KMP算法的核心在于利用模式串的特点,匹配失效时不是把模式串简单地后移一位,而是根据已匹配的部分进行移位。

        T:ababacabacadagh        S:abacab

                                                            ababadabacadagh

                               abacab

朴素算法中模式串S失效后,模式串向后一位,如下图

                           ababadabacadagh
                                 abacab

而在KMP算法中,模式串如下移位

                          ababadabacadagh

                                   abacab

由上图可知,字符比较的位置不变,依然在T的第6个字符,模式串向后移动两个位置。时间复杂度可以达到O(n)

进行这样的移位,需要先构造模式串的失效指针fail。

构造失效指针代码如下:

    int i,j;
    fail[0]=-1;
    j=-1;i=0;
    while(i<n)
    {
        if(j==-1||ch[i]==ch[j])
        {
            i++;
            j++;
            fail[i]=j;
        }
        else j=fail[j];
    }

失效指针的使用:

        i=0;j=-1;
        while(i<len(T))
        {
            while(j!=-1&&s[i]!=ch[j])j=fail[j];
            i++;
            j++;
            if(j==len(S))
            {
               //对应操作
            }
        }

通过KMP算法,单模式串匹配的时间复杂度就降为O(n+m)。

而在k模式串的情况下,可以对每个模式串使用KMP算法来进行匹配,设l=|所有模式串的长度之和|,则时间复杂度为O(kn+l)。

在模式串较多的时候,该方法就不好用了。


AC自动机就可以解决多模式串匹配问题。

可以使用Trie树改造来实现AC自动机。

代码如下:

struct ACTree
{
    int ch[maxnode][maxc];//ch[i][j]为i结点指向字符j的边,如果不存在则设为非法值
    int cot;//记录结点数量
    int fail[maxnode];//失效指针
    int val[maxnode];//结点储存的信息
    int newnode()
    {
        for(int i=0;i<maxc;i++)
            ch[cot][i]=-1;
        val[cot]=0;
        return cot++;
    }
    void init(){cot=0;newnode();}
    int idx(char c)
    {
        返回对应字符下标值
    }
    void insert(char *s)
    {
        int u=0,n=strlen(s);
        int v;
        for(int i=0;i<n;i++)
        {
            v=idx(s[i]);
            if(ch[u][v]==-1)
            {
                ch[u][v]=newnode();
            }
            u=ch[u][v];
        }
        val[u]=1;//根据情况设置
    }
    void build()//构造AC自动机
    {
        int u=0;
        queue<int>q;
        fail[0]=0;//根结点的失效指针指向根结点
        for(int i=0;i<maxc;i++)
        {
            if(ch[0][i]==-1)
                ch[0][i]=0;
            else
            {
                fail[ch[0][i]]=0;//根结点下一个字符结点的失效指针指向根结点
                q.push(ch[0][i]);
            }
        }
        while(!q.empty())
        {
            u=q.front();q.pop();
            for(int i=0;i<maxc;i++)
            {
                if(ch[u][i]==-1)
                    ch[u][i]=ch[fail[u]][i];
                else
                {
                    fail[ch[u][i]]=ch[fail[u]][i];
                    q.push(ch[u][i]);
                }
            }
        }
    }
    void match(char *s)
    {
        int n=strlen(s);
        int u=0,v,temp;
        for(int i=0;i<n;i++)
        {
            v=idx(s[i]);
            u=ch[u][v];
            temp=u;
            while(temp)
            {
                if(val[temp]>0)
                {
                    //对应操作
                }
                temp=fail[temp];
            }
        }
    }
};

AC自动机构造时,把结点不存在的边指向。在匹配时就不需要判断。即ch[u][i]=ch[fail[u]][i],不存在的边指向失效指针的对应字符结点。

这样匹配的时间复杂度为O(n),构造AC自动机的时间复杂度为O(l)。

总时间复杂度是O(l+n)。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值