以下是模板:
#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。