Trie树(字典树)
树中任一结点p都对应于一个字符串S,S由从根出发走到p所经过的边上的字符构成
数据结构
struct trienode { trienode * child[26] ; //假设所有字符就是26个小写字母 trienode() { memset(child,0,sizeof(child)); } };
操作
插入串
1 void build(string s, trienode * root) 2 { 3 trienode* p=root; 4 for (int i=0;i<s.size();++i) 5 { 6 if (p->child[s[i]-’a’]== NULL) 7 p->child[s[i] -’a’] = new trienode(); //初始化新的节点 8 p=p->child[s[i] -’a’]; 9 } 10 }
(复杂度为模式串长度)
Trie图(AC自动机)
可以由Trie树为基础构造
终止节点:每个模式串最后一个结点
危险结点:终止节点和前缀指向危险结点的结点。
包含前缀指针(next),结点p(字符串为S)的前缀指针指向的结点q的字符串为T满足:T是其他串中最长的S的一个后缀且不可等于S(可以为空串,即root)
求母串包含哪些模式串,将母串输入在Trie图上行走走到终止节点就表示匹配了相应的模式串
数据结构
int nNodesCount=0; struct CNode { CNode * pChilds[LETTERS]; CNode * pPrev; //前缀指针 bool bBadNode; //是否是危险节点 CNode() { memset(pChilds,0,sizeof(pChilds)); bBadNode = false; pPrev = NULL; } }Tree[MAXN];
操作
在Trie树上添加前缀指针
- 用BFS按深度由浅到深求每一个结点的前缀指针
- 对于结点P,设其父节点与其连边上的字符为ch,找到父节点的前缀指针指向的结点看它与儿子的边有没有一条是ch的,如果有就把这个儿子作为P的前缀指针指向,没有继续用这个结点的前缀指针向上找。
1 void BuildDfa( ) { //在trie树上加前缀指针 2 for( int i = 0;i < LETTERS ;i ++ ) 3 Tree[0].pChilds[i] = Tree + 1; 4 Tree[0].pPrev = NULL; 5 Tree[1].pPrev = Tree; 6 deque<CNode * > q; 7 q.push_back(Tree+1); 8 while( ! q.empty() ){ 9 CNode * pRoot = q.front(); 10 q.pop_front(); 11 for( int i = 0; i < LETTERS ; i ++ ) { 12 CNode * p = pRoot->pChilds[i]; 13 if( p) { 14 CNode * pPrev = pRoot->pPrev; 15 while( pPrev->pChilds[i] == NULL) 16 pPrev = pPrev->pPrev; 17 p->pPrev = pPrev->pChilds[i]; 18 if( p->pPrev-> bBadNode) 19 p-> bBadNode = true; 20 //前缀指针指向的节点是危险节点,则自己也是危险节点 21 q.push_back(p); 22 } 23 } 24 } //对应于while( ! q.empty() ) 25 }
母串匹配
- 从root和母串第一个字符出发
- 若当前点P不存在通过当前字符连接的儿子,则考察P的前缀指针指向的结点Q,依次类推直到找到。母串指针指向下一个字符继续遍历。
- 如果遍历过程中经过了某个终止结点,说明S包含该终止结点代表的模式串。
- 若遍历过程中经过了某个非终止结点的危险结点则可以顺着前缀指针找到一个终止结点,这个终止结点代表的串被S包含。
- 复杂度:S的长度
1 bool SearchDfa(char * s) 2 {//返回值为true则说明包含模式串 3 CNode * p = Tree + 1; 4 for( int i = 0; s[i] ; ++i) { 5 while( p->pChilds[s[i]-'a'] == NULL) 6 p = p->pPrev; 7 p = p->pChilds[s[i]-'a']; 8 if( p-> bBadNode) 9 return true; 10 } 11 return false; 12 }