标签:
trie图用于解决多模式匹配问题。设有N个长度不超过L的模式串,匹配串长为M,那么用trie图解决多模式匹配问题的复杂度为O(N*L+M).
思路:
trie图的基础是trie树。
1.用trie树实现多模式匹配
首先建立N个模式串的trie树。设匹配串为s,我们枚举匹配起始位置i,在trie树中依次去查询字典中是否有字符串&str[i]的前缀。这样做的复杂度为O(M*L).
2.用trie图实现多模式匹配
对于上述用trie树实现多模式匹配的方式,当我们沿trie树匹配的过程中找不到当前字符对应的边的时候,我们的做法是改变起始位置i的值,从trie树根节点从头再来。
但其实此时我们已经从s的当前起始点i开始成功匹配了k个字符,那么我们在枚举下一个起点i+1的时候,意味着前k-1个字符已经在上一次枚举中匹配过了,就像kmp算法一样,如果我们能利用好这个信息,就能大大的减小时间复杂度。
2.1后缀节点的概念
假设从根节点到当前节点A的路径所对应的字符串为"abcde",那么"bcde"所对应的路径末节点就是节点A的后缀节点。但是,trie树中可能并不存在"bcde"这样一条从根节点开始的路径,此时A的后缀节点是"bcde"的后缀节点,即后缀节点的后缀节点,如此递归。特别的,根节点的后缀节点为根节点,深度为1的节点的后缀节点也为根节点。
2.2后缀节点的求法
采用广度优先遍历来求解后缀节点,这样,当我们要求trie树中第i层节点的后缀节点的时候,第0~i-1层的所有点的后缀节点都已知了。我们可以根据父节点来求当前节点的后缀节点:假设父节点到当前节点的边为ch,那么当前节点的后缀节点就是父节点的后缀节点通过ch这样一条边所到达的节点。然而,父节点的后缀节点有可能并不存在ch这样一条边,此时,我们需要看后缀节点的后缀节点是否存在ch这样一条边,如此递归。但是,如果我们每次都这样递归来做未免显得有些麻烦,所以,在bfs的时候,对于每个节点,我们用一个数组记录下从当前节点经过每一个ch所到达的节点,这里的“到达”的理解:当存在ch边的时候,就是一般意义的到达,当不存在ch边的时候,这里的“到达”应该理解为其后缀节点经过ch这样一条边所到达的节点。如此一来,后缀节点的求解就很简单了。
还有一个问题需要注意:当当前节点不存在匹配字符所对应的边即不匹配的时候,我们直接跳转到其后缀节点,然后继续往下匹配,我们这种做法会可能造成一种后果:那就是从根节点到后缀节点的路径中可能存在标记节点,而被我们忽略了。解决这个问题的方法是“在求解当前后缀节点的过程中,若求得的后缀节点是标记节点,那么当前节点也要被标记”。
2.3实现多模式匹配
求得了所有的后缀节点,相当于我们的trie图已经构建完毕了。此时,多模式匹配问题,就只需要遍历匹配串,从trie树的根节点开始逐个字符匹配,当匹配不成功的时候,就跳转到后缀节点继续匹配,直到匹配成功或者匹配串遍历结束。
我的代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 using namespace std; 6 7 #define MAXN 1000005 8 #define MAX_CH 26 9 10 char str[MAXN]; 11 int nodecnt; 12 13 struct trieNode 14 { 15 trieNode *p[MAX_CH]; 16 trieNode *pFather; 17 int ch; 18 bool isMarked; 19 trieNode *postfix; 20 trieNode *next[MAX_CH]; 21 22 void init() 23 { 24 for(int i=0; i<MAX_CH; ++i) 25 { 26 p[i] = NULL; 27 next[i] = NULL; 28 } 29 isMarked = 0; 30 postfix = NULL; 31 } 32 }node[MAXN]; 33 34 struct Trie 35 { 36 trieNode *root; 37 38 void init() 39 { 40 root = &node[0]; 41 root->init(); 42 root->pFather = NULL; 43 root->ch = -1; 44 45 nodecnt = 1; 46 } 47 48 //向trie中插入单词 49 void insert(char *str) 50 { 51 trieNode *pNow = root; 52 int len = strlen(str); 53 for(int i=0; i<len; ++i) 54 { 55 int id = str[i]-‘a‘; 56 if(pNow->p[id]==NULL) 57 { 58 pNow->p[id] = &node[nodecnt++]; 59 pNow->p[id]->init(); 60 pNow->p[id]->pFather = pNow; 61 pNow->p[id]->ch = id; 62 } 63 if(i==len-1) 64 { 65 pNow->p[id]->isMarked = 1; 66 } 67 pNow = pNow->p[id]; 68 } 69 } 70 71 //求某个节点的后缀节点 72 void getPostfix(trieNode *pNow) 73 { 74 if(pNow==root) 75 { 76 pNow->postfix = pNow; 77 78 for(int i=0; i<MAX_CH; ++i) 79 { 80 if(pNow->p[i]!=NULL) 81 { 82 pNow->next[i] = pNow->p[i]; 83 } 84 else 85 { 86 pNow->next[i] = pNow; 87 } 88 } 89 } 90 else 91 { 92 if(pNow->pFather==root) 93 { 94 pNow->postfix = root; 95 } 96 else 97 { 98 pNow->postfix = pNow->pFather->postfix->next[pNow->ch]; 99 } 100 if(pNow->postfix->isMarked) 101 { 102 pNow->isMarked = 1; 103 } 104 105 for(int i=0; i<MAX_CH; ++i) 106 { 107 if(pNow->p[i]!=NULL) 108 { 109 pNow->next[i] = pNow->p[i]; 110 } 111 else 112 { 113 pNow->next[i] = pNow->postfix->next[i]; 114 } 115 } 116 } 117 } 118 119 //广度优先遍历,求所有节点的后缀节点, 构造trie图 120 void bfs() 121 { 122 queue<trieNode*> q; 123 q.push(root); 124 while(!q.empty()) 125 { 126 trieNode *pNow = q.front(); 127 getPostfix(pNow); 128 q.pop(); 129 for(int i=0; i<MAX_CH; ++i) 130 { 131 if(pNow->p[i]!=NULL) q.push(pNow->p[i]); 132 } 133 } 134 } 135 136 //多模式匹配 137 bool match(char *str) 138 { 139 trieNode *pNow = root; 140 int len = strlen(str); 141 142 for(int i=0; i<len; ++i) 143 { 144 int id = str[i]-‘a‘; 145 if(pNow->p[id]==NULL) 146 { 147 pNow = pNow->postfix; 148 if(pNow->isMarked) return 1; 149 } 150 pNow = pNow->next[id]; 151 if(pNow->isMarked) return 1; 152 } 153 return 0; 154 } 155 }trie; 156 157 int main() 158 { 159 int n; 160 while(scanf("%d", &n)!=EOF) 161 { 162 trie.init(); 163 while(n--) 164 { 165 scanf("%s", str); 166 trie.insert(str); 167 } 168 trie.bfs(); 169 scanf("%s", str); 170 puts(trie.match(str)?"YES":"NO"); 171 } 172 return 0; 173 }
题目来源:http://hihocoder.com/problemset/problem/1036
标签:
原文地址:http://www.cnblogs.com/pczhou/p/4295189.html