一种空间复杂度为O(logn)的线性字符串匹配算法

记号

为空字符串序列

Q为所有字符串序列构成的集合

对于AQ,记|A|为其中字符串个数

对于{1lrnA={a1,a2,...,an}Q,记{i=lrai=alal+1...arA[l,r]={al,al+1,...,ar}


皮配函数

定义辅助函数g:QQ,对于A={a1,a2,...,an}Q

  • A=a1=a2=...=an,则g(A)=

  • 否则,取最小的i[1,n]满足aia1,取j[1,ni+1]满足A[1,i]=A[j,j+i1]

    设依次为b1<b2<...<bk,则g(A)={j=b1b21aj,j=b2b31aj,...,j=bk1bk1aj}

性质1:|g(A)||A|2

(参考g中的定义)

  • A=a1=a2=...=an时显然成立

  • 否则,若j[1,k),bj+i>bj+1,则ai=abj+i1=a1,矛盾

    换言之,有j[1,k),bj+ibj+1,进而(k1)i+1bkni+1

    化简得kni,显然i2,即|g(A)|=k1<n2

定义皮配集合Φ={}{(X,Y,Pz)X,YQ,PzΦ}

对于PΦ,记|P|={0P=|Pz|+1P=(X,Y,Pz)

定义皮配函数f:QΦ,对于A={a1,a2,...,an}Q

  • A=,则f(A)=
  • 否则,若a1=a2=...=an,则f(A)=(A,A,)
  • 否则,(参考g中的定义)f(A)=(A[1,i],A[bk,n],f(g(A)))

性质2:|f(A)|{0A=log2|A|+1A

  • A=A中字符串均相同时显然成立
  • 否则,对|A|从小到大归纳,当g(A)=时显然成立
  • 否则,根据归纳假设及性质1,有|f(A)|log2|g(A)|+2log2|A|+1

增量法

已知f(A),求f(A+{s})

  • f(A)=,则f(A+{s})=({s},{s},)

  • 否则,设f(A)=(X,Y,Pz),其中Y={y1,y2,...,yn}

    X中字符串均相同,则f(A+{s})=(X+{s},Y+{s},Pz)

  • 否则,若X不为Y+{s}的后缀,则f(A+{s})=(X,Y+{s},Pz)

  • 否则,f(A+{s})=(X,X,f(g(A)+{i=1n|X|+1yi}))

    后者即"已知f(g(A)),求f(g(A)+{i=1n|X|+1yi})"的子问题,可以递归处理

T(A,s)为上述过程的递归次数(包括初始加入)

定义势能函数φ:QN,其中φ(A)={0A=|A|+φ(g(A))A

性质3:T(A,s)=φ(A+{s})φ(A)

(参考增量法过程中的定义)

  • |A|从小到大归纳,当不进行递归时显然成立

  • 否则,记s=i=1n|X|+1yi,则g(A)+{s}=g(A+{s}),根据归纳假设

    T(A,s)=φ(g(A)+{s})φ(g(A))+1=φ(A+{s})φ(A)

换言之,对于A={a1,a2,...,an}Q,增量法求f(A)的总递归次数即

i=1nT(A[1,i1],ai)=i=1nφ(A[1,i])φ(A[1,i1])=φ(A)

性质4:φ(A)2|A|

  • |A|从小到大归纳,当A=时显然成立
  • 否则,根据归纳假设及性质1,有φ(A)=|A|+2|g(A)|2|A|

综上,增量法求f(A)的总递归次数(均摊)为O(n)


皮配结论

定义Φ上的皮配关系ξΦ×Φ,对于Ps,PtΦ

  • Ps=,则(Ps,Pt)ξ

  • 否则,若Pt=,则(Ps,Pt)ξ

  • 否则,设{Ps=(Xs,Ys,Pzs)Pt=(Xt,Yt,Pzt)

    Xs中字符串均相同,则(Ps,Pt)ξXsYt的后缀

  • 否则,(Ps,Pt)ξYs=Yt(Pzs,Pzt)ξ

皮配结论:|S||T|,则ST的后缀(f(S),f(S+T))ξ

{S={s1,s2,...,sn}T={t1,t2,...,tm},{Ps=f(S)=(Xs,Ys,Pzs)Pt=f(S+T)=(Xt,Yt,Pzt)

  • Ps=Pt=时显然成立

  • 否则,有|Yt||Xs|且为S+T的后缀

    Xs中字符串均相同时,有S=Xs,进而显然成立

  • 否则,对|S|从小到大归纳

    ST的后缀,则Xs=S[j,j+|Xs|1]=(S+T)[j+m,j+m+|Xs|1],即出现位置相同

    换言之,需Ys=Ytg(S)g(S+T)的后缀,由归纳假设后者即(Pzs,Pzt)ξ

    另一方面,若满足上述条件,显然ST的后缀


字符串匹配

将给定字符串转换为字符串序列(每个元素长度为1),设为{S={s1,s2,...,sn}T={t1,t2,...,tm}

根据结论,用增量法求出f(S)f(S+T[1,i]),并根据ξ的定义判定即可

在实现中,需O(1)实现增量法的每层递归,具体细节如下——

  • 字符串以哈希的形式存储,即可O(1)拼接和判断是否相同

  • 对于f(A)=(X,Y,Pz),设{X={x1,x2,...,xn}Y={y1,y2,...,ym}

    X满足x1=x2=...=xn1,仅需维护n,x1xn即可

    Y需判定X是否为Y+{s}的后缀,并求i=1mn+1yi(若成立)

    维护Y末尾s1的个数c(对n1min)和i=1myi,判定即cn1s=xn

    另外,为了避免减法,可以额外维护i=1mcyi

  • 由于f(S)固定,ξ的判定可以转换为若干个条件,在增量时维护满足的条件数即可

综上,时间复杂度为O(n+m),空间复杂度为O(log(n+m))

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=30,B=47,mod=998244353;
int n,m,t,cnt,ans;
struct String{
	int s,H;
	String(){
		s=1,H=0;
	}
	String(int c){
		s=B,H=c;
	}
	String(int _s,int _H){
		s=_s,H=_H;
	}
	bool operator == (const String &n)const{
		return (s==n.s)&&(H==n.H);
	}
	String operator + (const String &n)const{
		return String((ll)s*n.s%mod,(H+(ll)s*n.H)%mod);
	}
};
struct Seqx{
	bool flag;int c;String a,b;
	Seqx(){}
	Seqx(String &s){
		flag=0,c=1,a=s,b=String();
	}
	void add(String &s){
		if (a==s)c++;
		else flag=1,b=s;
	}
}X[N];
struct Seqy{
	int c;String a,b,a0;
	Seqy(){}
	Seqy(String &s){
		c=1,a=s,b=a0=String();
	}
	bool add(String &s,Seqx &x){
		a=a+s;
		if (!x.flag){
			if (s==x.a)c++;
			else c=0,b=a0=a;
		}
		else{
			if (s==x.a){
				if (c<x.c)c++;
				else b=b+s;
			}
			else{
				if ((s==x.b)&&(c==x.c))return 1;
				c=0,b=a;
			}
		}
		return 0;
	}
}Y[N];
struct Data{
	Seqx x;Seqy y;Data *z;
}*Ps,*Pt;
char Getchar(){
	char c=getchar();
	while ((c<'a')||(c>'z'))c=getchar();
	return c;
}
bool check(int d,Seqy &y){
	return (X[d].flag ? Y[d].a==y.a : X[d].c<=y.c);
}
void add(Data **P,String s,int d,int p){
	if ((p)&&(d>=t))return;
	if ((p)&&(!check(d,(*P)->y)))cnt--;
	if ((*P)==NULL)(*P)=new Data{Seqx(s),Seqy(s),NULL};
	else{
		if ((*P)->y.add(s,(*P)->x)){
			add(&(*P)->z,(*P)->y.b,d+1,p);
			(*P)->y.c=0,(*P)->y.a=(*P)->y.b=(*P)->y.a0;
		}
		if (!(*P)->x.flag)(*P)->x.add(s);
	}
	if ((p)&&(!check(d,(*P)->y)))cnt++;
}
void get(Data *P){
	if (P==NULL)return;
	X[t]=P->x,Y[t++]=P->y,get(P->z);
}
void get(Data **P,int d){
	if (d==t)return;
	(*P)=new Data{X[d],Y[d],NULL},get(&(*P)->z,d+1);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		int c=Getchar()-'a'+1;
		add(&Ps,String(B,c),0,0);
	}
	t=cnt=0,get(Ps),get(&Pt,0);
	for(int i=1;i<=m;i++){
		int c=Getchar()-'a'+1;
		add(&Pt,String(B,c),0,1);
		if ((i>=n)&&(!cnt))ans++;
	}
	printf("%d\n",ans);
	return 0;
}
posted @   PYWBKTDA  阅读(356)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示