以下是模板:
#include<stdio.h>
#include<string.h>
//后缀自动机Suffix Automaton
/*
注意:
(1)CHARSZ是字符个数 idx()可以对字符进行编码。默认只接受小写字母
(2)调用SAM::buildSAM即可建树
(3)调用SAM::clearSAM清空内存。
*/
#define CHARSZ 26
int idx(char c){return c-'a';}
struct State{
State* par,*go[CHARSZ];
int val,f;
//method
State(int _val){
par=0;
val=_val;
memset(go,0,sizeof(go));
}
void clear_all(){
for (int i=0;i<CHARSZ;i++)
if(go[i]) go[i]->clear_all();
delete this;
}
}*root,*last;
class SAM{
private:
State* root;
State* last;
void extend(int w);
public:
SAM(){root=last=NULL;}
void buildSAM(char* s); //初始化SAM
void clearSAM(); //释放SAM
State* getRoot(){return root;}
};
void SAM::extend(int w){
State* p=last;
State* np=new State(p->val+1);
while(p&&p->go[w]==0){
p->go[w]=np;
p=p->par;
}
if (p==0) np->par=root;
else{
State* q=p->go[w];
if (p->val+1 == q->val) np->par=q;
else{
State* nq=new State(p->val+1);
memcpy(nq->go,q->go,sizeof(q->go));
nq->par=q->par;
q->par=nq;
np->par=nq;
while (p&& p->go[w]==q){
p->go[w]=nq;
p=p->par;
}
}
}
last=np;
}
void SAM::buildSAM(char* s){
root=last=new State(0);
int slen=strlen(s);
for (int i=0;i<slen;i++)
extend(idx(s[i]));
}
void SAM::clearSAM(){if (root) root->clear_all(); root=NULL;}
//-----------------end---------------------
int main(){
SAM sam;
sam.buildSAM("hello,world");
//以下代码判断是否是hello,world的子串。
while (true){
char s[100];
scanf("%s",s);
State* cur=sam.getRoot();
for (int i=0;i<strlen(s);i++){
cur=cur->go[idx(s[i])];
if (!cur)break;
}
if (cur)printf("yes\n");else printf("no\n");
}
return 0;
}
以上述图为例
STR=aabbabd
一、节点、蓝色边、绿色边概念
节点个数是线性O(n),边个数也是线性的。
每个节点接受一个STR的子串集合,SAM接受(即走蓝色边)且仅接受STR所有的子串。
节点x接受的子串性质:
(1) 这些子串总是相互为后缀
(2) 令最长子串为t1,t2,...tk,最多的为tp,tp+1...tk。
则所有子串为ti,ti+1,....,tk,1<=i<=p。
如节点6,包含的所有子串为:ba,bba,abba,aabba。
(3) suffix links
绿色边,某个状态中,最长子串某个后缀如果不在此状态表示的集合中,则用suffix links连接过去。
如节点6,包含的所有子串为:ba,bba,abba,aabba。
还有两个子串 "a" 和 "" 就是通过绿色边连接的:6->1->0。
二、线性构造算法
令STR=aabbabd
1、由前i个字符构造的SAM(i)和前i+1个字符构造的SAM(i+1) 两个后缀自动机中:
有:(1)SAM(i)是SAM(i+1)的子图,故直接在SAM(i)上扩展即可得到SAM(i+1)
(2)SAM(i) 接受 STR[1..i]的所有子串,故SAM(i+1)只需要添加东西,使之接受所有以STR[i+1]结尾的子串即可。
另外,由于新添的字符,可能导致原先节点的集合发生分裂,故需要在适当的时候,将right集合发送变动的节点拆分成两个节点。
例子:SAM(5) 接受aabba的所有的子串,
SAM(6)需要再SAM(5)的基础上,额外接受b,ab,bab,bbab,abbab,aabbab这六个子串,即需要在
又知道这6个子串只需要从节点6、节点1,节点S 各扩展1条b出去即可。
[1] 节点6(ba,bba,abba,aabba)+b= (bba,bbab,abbab,aabbab)
[2]节点1(a)+b=(ab)
[3]节点S("")+b=(b)
而6->1->S 恰好是图中的绿色边(也就是fail指针),故在从节点6扩展出去时,只需要不断走fail指针,然后扩展,直到处理完节点S。