首页 > 编程语言 >一种空间复杂度为$O(\log n)$的线性字符串匹配算法

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

时间:2023-02-10 22:13:30浏览次数:44  
标签:... le log 复杂度 varphi 算法 cases empty String

记号

记\(\empty\)为空字符串序列

记\(Q\)为所有字符串序列构成的集合

对于\(A\in Q\),记\(|A|\)为其中字符串个数

对于\(\begin{cases}1\le l\le r\le n\\ A=\{a_{1},a_{2},...,a_{n}\}\in Q \end{cases}\),记\(\begin{cases}\sum_{i=l}^{r}a_{i}=a_{l}a_{l+1}...a_{r}\\A_{[l,r]}=\{a_{l},a_{l+1},...,a_{r}\}\end{cases}\)

对于\(\begin{cases}A=\{a_{1},a_{2},...,a_{n}\}\\B=\{b_{1},b_{2},...,b_{m}\}\end{cases} \in Q\),记\(A+B=\{a_{1},a_{2},...,a_{n},b_{1},b_{2},...,b_{m}\}\)

对于\(\begin{cases}A=\{a_{1},a_{2},...,a_{n}\}\\B=\{b_{1},b_{2},...,b_{m}\}\end{cases} \in Q\),称\(A\)为\(B\)的后缀当且仅当\(n\le m\)且\(\forall i\in [1,n],a_{i}=b_{m-n+i}\)


皮配函数

定义辅助函数\(g:Q\rightarrow Q\),对于\(A=\{a_{1},a_{2},...,a_{n}\}\in Q\)

  • 若\(A=\empty\)或\(a_{1}=a_{2}=...=a_{n}\),则\(g(A)=\empty\)

  • 否则,取最小的\(i\in [1,n]\)满足\(a_{i}\ne a_{1}\),取\(j\in [1,n-i+1]\)满足\(A_{[1,i]}=A_{[j,j+i-1]}\)

    设依次为\(b_{1}<b_{2}<...<b_{k}\),则\(g(A)=\{\sum_{j=b_{1}}^{b_{2}-1}a_{j},\sum_{j=b_{2}}^{b_{3}-1}a_{j},...,\sum_{j=b_{k-1}}^{b_{k}-1}a_{j}\}\)

性质1:\(|g(A)|\le \lfloor\frac{|A|}{2}\rfloor\)

(参考\(g\)中的定义)

  • 当\(A=\empty\)或\(a_{1}=a_{2}=...=a_{n}\)时显然成立

  • 否则,若\(\exists j\in [1,k),b_{j}+i>b_{j+1}\),则\(a_{i}=a_{b_{j}+i-1}=a_{1}\),矛盾

    换言之,有\(\forall j\in [1,k),b_{j}+i\le b_{j+1}\),进而\((k-1)i+1\le b_{k}\le n-i+1\)

    化简得\(k\le \lfloor\frac{n}{i}\rfloor\),显然\(i\ge 2\),即\(|g(A)|=k-1<\lfloor\frac{n}{2}\rfloor\)

定义皮配集合\(\Phi\)满足\(\Phi=\{\varnothing\}\cup \{(X,Y,P_{z})\mid X,Y\in Q,P_{z}\in \Phi\}\)

对于\(P\in \Phi\),记\(|P|=\begin{cases}0&P=\varnothing\\|P_{z}|+1&P=(X,Y,P_{z})\end{cases}\)

定义皮配函数\(f:Q\rightarrow \Phi\)

  • 若\(A=\empty\),则\(f(A)=\varnothing\)
  • 否则,若\(A\)中字符串均相同,则\(f(A)=(A,A,\varnothing)\)
  • 否则,(参考\(g\)中的定义)\(f(A)=(A_{[1,i]},A_{[b_{k},n]},f(g(A)))\)

性质2:\(|f(A)|\le \begin{cases}0&A=\empty\\ \lfloor\log_{2}|A|\rfloor+1&A\ne \empty\end{cases}\)

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

增量法

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

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

  • 否则,设\(f(A)=(X,Y,P_{z})\),其中\(Y=\{y_{1},y_{2},...,y_{n}\}\)

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

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

  • 否则,\(f(A+\{s\})=(X,X,f(g(A)+\{\sum_{i=1}^{n-|X|+1}y_{i}\}))\)

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

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

定义势能函数\(\varphi:Q\rightarrow N\),其中\(\varphi(A)=\begin{cases}0&A=\empty\\ |A|+\varphi(g(A))&A\ne \empty\end{cases}\)

性质3:\(T(A,s)=\varphi(A+\{s\})-\varphi(A)\)

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

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

  • 否则,记\(s'=\sum_{i=1}^{n-|X|+1}y_{i}\),则\(g(A)+\{s'\}=g(A+\{s\})\),根据归纳假设

    \[T(A,s)=\varphi(g(A)+\{s'\})-\varphi(g(A))+1=\varphi(A+\{s\})-\varphi(A) \]

换言之,对于\(A=\{a_{1},a_{2},...,a_{n}\}\in Q\),增量法求\(f(A)\)的总递归次数即

\[\sum_{i=1}^{n}T(A_{[1,i-1]},a_{i})=\sum_{i=1}^{n}\varphi(A_{[1,i]})-\varphi(A_{[1,i-1]})=\varphi(A) \]

性质4:\(\varphi(A)\le 2|A|\)

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

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


皮配结论

定义\(\Phi\)上的皮配关系\(\xi\subseteq\Phi\times \Phi\),对于\(P_{s},P_{t}\in \Phi\)

  • 若\(P_{s}=\varnothing\),则\((P_{s},P_{t})\in \xi\)

  • 否则,若\(P_{t}=\varnothing\),则\((P_{s},P_{t})\not\in \xi\)

  • 否则,设\(\begin{cases}P_{s}=(X_{s},Y_{s},P_{zs})\\P_{t}=(X_{t},Y_{t},P_{zt})\end{cases}\)

    若\(X_{s}\)中字符串均相同,则\((P_{s},P_{t})\in \xi\iff X_{s}\)为\(Y_{t}\)的后缀

  • 否则,\((P_{s},P_{t})\in \xi \iff Y_{s}=Y_{t}\)且\((P_{zs},P_{zt})\in \xi\)

皮配结论:若\(|S|\le |T|\),则\(S=T_{[|T|-|S|+1,|T|]}\iff (f(S),f(S+T))\in \xi\)

记\(\begin{cases}S=\{s_{1},s_{2},...,s_{n}\}\\T=\{t_{1},t_{2},...,t_{m}\}\end{cases},\begin{cases}P_{s}=f(S)=(X_{s},Y_{s},P_{zs})\\P_{t}=f(S+T)=(X_{t},Y_{t},P_{zt})\end{cases}\)

  • 当\(P_{s}=\varnothing\)或\(P_{t}=\varnothing\)时显然成立

  • 否则,有\(|Y_{t}|\ge |X_{s}|\)且为\(S+T\)的后缀

    当\(X_{s}\)中字符串均相同时,有\(S=X_{s}\),进而显然成立

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

    若\(S\)为\(T\)的后缀,则\(X_{s}=S_{[j,j+|X_{s}|-1]}=(S+T)_{[j+m,j+m+|X_{s}|-1]}\),即出现位置相同

    换言之,需\(Y_{s}=Y_{t}\)且\(g(S)\)为\(g(S+T)\)的后缀,由归纳假设即\((P_{zs},P_{zt})\in \xi\)

    另一方面,若满足上述条件,显然成立


字符串匹配

将给定字符串转换为字符串序列(每个元素长度为\(1\)),设为\(\begin{cases}S=\{s_{1},s_{2},...,s_{n}\}\\T=\{t_{1},t_{2},...,t_{m}\}\end{cases}\)

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

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

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

  • 对于\(f(A)=(X,Y,P_{z})\),设\(\begin{cases}X=\{x_{1},x_{2},...,x_{n}\}\\Y=\{y_{1},y_{2},...,y_{m}\}\end{cases}\)

    \(X\)满足\(x_{1}=x_{2}=...=x_{n-1}\),仅需维护\(n,x_{1}\)和\(x_{n}\)即可

    \(Y\)需判定\(X\)是否为\(Y+\{s\}\)的后缀,并求\(\sum_{i=1}^{m-n+1}y_{i}\)(若成立)

    维护\(Y\)末尾\(s_{1}\)的个数\(c\)(对\(n-1\)取\(\min\))和\(\sum_{i=1}^{m}y_{i}\),该判定即\(c\ge n-1\)且\(s=x_{n}\)

    另外,为了避免减法,可以额外维护\(\sum_{i=1}^{m-c}y_{i}\)

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

综上,时间复杂度为\(O(n+m)\),空间复杂度为\(O(\log n)\)(默认\(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;
}

标签:...,le,log,复杂度,varphi,算法,cases,empty,String
From: https://www.cnblogs.com/PYWBKTDA/p/17110386.html

相关文章