初等字符串匹配专题小结[KMP][Manacher][Tire Tree][AC Automation]

刷了3天的字符串匹配题。

为了下面继续切题,小的先小结一些。

字符串匹配的最基础算法是枚举(n^2)。

高深一点的是KMP。

KMP在数据结构课上学过,由于老师只是负责教学,不负责解答他不懂的问题,于是KMP就这么被我搁置一边了。ACM这么多年了,一直不懂这些基础的算法,实在有愧与心。

于是乎专程学习了一下KMP。

先说说KMP的主要思想。KMP用于模式串的匹配。

下面看看一个字符串:(1)AACAACAAB;

我们需要查找的串为:(2)AACAAB;

首先顺序匹配:

(1)AACAACAAB

(2)AACAA

到这里都顺利匹配上了,我们肉眼观察当(2)继续匹配时,'B'和'C'是不匹配的。那么怎样滑动呢?

对于串(2)我们可以发现'B'之前的字符'AA',与串(1)的'C'前面的两个字符是一样的,那么可以这么滑动....

(1)AACAACAAB

(2)        AA

然后继续匹配发现完全匹配了......

好了那么怎么滑动呢?构建一个next数组,记录滑动下标。

可以用一句话来说明:

在J字符的左边有[0,I-1]与[J-I-1,J-1]相同的话,下次J失配时,就可以滑动到I。

因为是滑动到J才失配,也就是说,J之前的所有字符串都是和主串相匹配的。由此,只要在本串中找到前缀和主串相匹配的(一定是部分匹配)选择滑动就可以了。

下面是构建next的函数。T是模式串

void setNext()
{
     int j=0,k=-1;
     next[0]=-1;
     while( j<lenT )
     {
            if( k==-1||t[j]==t[k] )
                next[++j]=++k;
            else
                k=next[k];
     }
}
当失配则回退,匹配则赋值继续前进。

下面是KMP的匹配模版

int kmp()
{
    int i=0,j=0;
    cnt=0;
    while( i<lenS&&j<lenT )
    {
           if( j==-1 || s[i]==t[j] )
               i++,j++;
           else
               j=next[j];
    }
    if( j>lenT )
	return i-lenT;
    else
	return -1;
}
匹配则继续,失配则滑动。

KMP主要用来解决的问题

1.主串中模式串出现的位置

2.主串中出现模式串的次数

3.主串分割成多少个模式串

4.模式串中前缀的循环次数

以上为KMP.......  写得不好啊........


好了接下来Manacher;

这个算法主要是用于计算回文串。运用了回文串的性质。

假设我们有一个回文串以id为中心,p[id]为以id为中心的回文串的半径。

下面给一个回文串:

             id  p[id]

   |<----|----->|

CABAAKAABAA

可以看出回文串为ABAAKAABA中心为K。

好了我们以mx=id+p[id],以id为中心的回文字符串的最右控制范围。

现在看K的右边那个字符串'B'。这个B实在mx之内的,所以还是受到了id的控制!

所以这个B的性质与B关于id的对称点左边的B有关。为啥?因为是回文嘛~两边对称。

通过肉眼,p['B']=1;所以右边这个B的左右两边也和左边的B相似。

但是仅限于当右边的B的右边界还在mx内时。

为啥?

看下面

AABAAKAABAC

这个字符串左边的p[B]=2;而右边的B显然没有这么广的控制范围,因为超过了mx的控制范围了。

所以右边p[B]的控制范围在与右边界的距离,和对称点的控制范围内取一个最小的就可以了。

但是对于原来的字符串,右边的B实际范围是还可以拓展的。所以继续拓展就好了。

拓展完后会发现新的回文串的最右边界超过了mx,此时记录更新就好了。

从左到右扫完后,取出最大的p[id]就好。再处理一下就好了。

为了避免判断奇偶性,在串中插入不常用字符'#','$'什么的.... 就这样。

#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;

int p[2222222];
char str[1111111],str1[2222222];
int len;

void Init()
{
 	 str1[0]='$';
 	 str1[1]='#';
 	 len=2;
 	 for( int i=0;str[i]!=0;i++ )
 	 {
	  	  str1[len++]=str[i];
	  	  str1[len++]='#';
	 }
	 str1[len]=0;
}

int main()
{
 	int T=0;
 	while( scanf("%s",&str)!=EOF )
 	{
	 	   if( strlen(str)==3 && str[0]=='E' && str[1]=='N' && str[2]=='D' )
	 	   	   break;
 		   //memset( str1,0,sizeof(str1) );
 		   memset( p,0,sizeof(p) );
	 	   Init();
	 	   int id,mx=0;
	 	   for( int i=1;i<len;i++ )
	 	   {
		   		if( mx>i )
		   			p[i]=min(p[(id<<1)-i],mx-i);
		   		else
		   			p[i]=1;
		   		while( str1[i-p[i]]==str1[i+p[i]] )
				   	   p[i]++;
			    if( mx<i+p[i] );
				{
				 	mx=i+p[i];
				 	id=i;
		 		}
	   	   }
	   	   printf( "Case %d: ",++T );
	   	   int ans=0;
	   	   for( int i=1;i<len;i++ )
	   	   		ans=max(p[i],ans);
	   	   printf( "%d\n",ans-1 );
  	}
 	return 0;
}

好吧下面继续讲==

所谓TireTree就是字典树,字母树。从根节点开始,每个节点代表一个字母,单词的第K个字母在树的第K层。

这只是一种数据结构。实现也不难。但却是后缀树与AC自动机的基础。

不多说,直接上模版。

#include<iostream>
#include<string>
#include<cstdio>
#define MAX 10
using namespace std;

char s[11111][11];
int allocp;
struct TireNode
{
       int nCount;
       TireNode *next[MAX];
};

TireNode Memeroy[1111111];
void InitTire( TireNode **root )
{
     *root=NULL;
}

TireNode *CreateTire()
{
         int i;
         TireNode *p=&Memeroy[allocp++];
         p->nCount=1;
         for( int i=0;i<MAX;i++ )
              p->next[i]=NULL;
         return p;
}
void InsertTire( TireNode **root,char *s )
{
     int i=0,k;
     TireNode *p;
     if( !(p=*root) )
         p=*root=CreateTire();
     
     while( s[i] )
     {
            k=s[i++]-'0';
            if( p->next[k] )
                p->next[k]->nCount++;
            else
                p->next[k]=CreateTire();
            p=p->next[k]; 
     }
}

bool SearchTire( TireNode **root,char *s )
{
     int i=0,k;
     TireNode *p=*root;
     int cnt=0; 
     while( s[i] )
     {
            k=s[i++]-'0';
            cnt=p->next[k]->nCount; 
            p=p->next[k];    
     }
     if( cnt==1 )
         return false;
     else
         return true; 
}

int main()
{
    int T;
    scanf( "%d",&T );
    while( T-- )
    {
           allocp=0;
           TireNode *root;
           root=NULL;
           int len=0;
           scanf( "%d",&len ); 
           for( int i=0;i<len;i++ )
           {
                scanf( "%s",&s[i] );
                InsertTire(&root,s[i]);
           }
           bool found=true;
           for( int i=0;i<len;i++ )
           {
                if( SearchTire(&root,s[i]) )
                {    
					found=false;
                    break;
				}
           }
           if( found==false )
               printf( "NO\n" );
           else
               printf( "YES\n" ); 
    }
    return 0;
}
累了。。。。AC自动机明天在写吧。。。

可以这么理解AC自动机就是在一棵字典树树上进行KMP......

先模版之.......

#include<iostream>
#include<cstdio>
#include<string.h>
#define MAX 26
using namespace std;

int root,tot;
struct node
{
       int fail;
       int cnt;
       int next[MAX];
       void init()
	   {
            memset( next,0,sizeof(next) );
            fail=-1;cnt=0;
       }
}Tire[5555555];
int queue[5555555];

void init(){
     root=tot=0;
     Tire[root].init();
}

void insert( int root,char *s ){
     int p=root;
     int i=0,k;
     while( s[i] )
	 {
            k=s[i++]-'a';
            if( !Tire[p].next[k] )
            {
                Tire[++tot].init();
                Tire[p].next[k]=tot;
            }
            p=Tire[p].next[k];
     }
     Tire[p].cnt++;
}

void build_ac_automation()
{
     int head,tail;
     head=tail=0;
     queue[tail++]=root;
     while( head<tail )
	 {
            int cur=queue[head++];
            for( int i=0;i<MAX;i++ )
			{
                 if( Tire[cur].next[i] )
				 {
                     int son=Tire[cur].next[i];
                     int p=Tire[cur].fail;
                     if( cur==root )
                         Tire[son].fail=root;
                     else
                         Tire[son].fail=Tire[p].next[i];
                     queue[tail++]=son;
                 }
                 else
				 {
                     int p=Tire[cur].fail;
                     if( cur==root )
                         Tire[cur].next[i]=root;
                     else
                         Tire[cur].next[i]=Tire[p].next[i];
                 }
            }
     }
}

int query( char *s )
{
    int i=0,k,p=root;
    int ret=0;
    while( s[i] )
    {
           k=s[i++]-'a';
           while( !Tire[p].next[k]&&p!=root )
               	  p=Tire[p].fail;
           p=Tire[p].next[k];
           if(p==-1)p=0;
           int temp=p;
           while( temp!=root&&Tire[temp].cnt!=-1 )
           {
                  ret+=Tire[temp].cnt;
                  Tire[temp].cnt=-1;
                  //sTire[temp].cnt=0;
                  temp=Tire[temp].fail;
           }
    }
    return ret;
}

char str[1111111];
int main(){
    int T;
    scanf( "%d",&T );
    while( T-- ){
           init();
           int N;
           scanf( "%d",&N );
           while( N-- )
           {
                 scanf( "%s",&str );
                 insert( root,str );
           }
           build_ac_automation();
           scanf( "%s",&str );
           printf( "%d\n",query(str) );
           //system("pause");
    }
    return 0;
}











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值