实现了一个Trie图(AC自动机)的模板。
Trie图用于多模式串匹配。当模式串数量为1,它退化为KMP自动机。
以下类Trie即为AC自动机模板类。
Trie<int numChar>(char** strs, int nstr, int (*ch2idx)(char))
构造一个AC自动机。
- numChar为AC自动机中所用的字符集合的字符个数。
- strs为若干个模式串
- nstr为模式串的数量
- ch2idx为转换方法,可将一个字符变为它在字符集合里的索引序号。
使用Match(char* str)
方法来匹配一个字符串。如果它包含某个模式串,就返回true。否则返回false。
我编写这个模板是用来复习AC自动机的基本原理。大家可以在本代码基础上修改TrieNode结构与Match方法部分内容,使得Match方法可以返回具体匹配了哪个/哪些字符串。
#include <memory.h>
#include <queue>;
template <int numChar>
struct TrieNode
{
TrieNode* sons[numChar];
bool dangerous;
TrieNode* next;
TrieNode() { memset(this, 0, sizeof(TrieNode)); }
~TrieNode() { for (int i = 0; i < numChar; i++) if (sons[i] != NULL) delete sons[i]; }
};
template <int numChar>
class Trie
{
private:
TrieNode<numChar>* root;
int (*ch2idx)(char);
// 往Trie树中添加一个字符串。需要提供转换函数ch2idx,将字符转换为它在字符集中的索引。
void AddStr(char* str, int (*ch2idx)(char))
{
TrieNode<numChar>* node = root;
for (int i = 0; str[i] != '\0'; i++) {
int idx = ch2idx(str[i]);
if (node->sons[idx] == NULL) node->sons[idx] = new TrieNode<numChar>();
node = node->sons[idx];
}
node->dangerous = true;
}
// 建立好Trie树后,构建Trie图
void Build()
{
std::queue<TrieNode<numChar>*> bfs;
bfs.push(root);
while (bfs.empty() == false) {
TrieNode<numChar>* front = bfs.front();
for (int i = 0; i < numChar; i++) {
TrieNode<numChar>* p = front->sons[i];
if (p == NULL) continue;
bfs.push(p);
p->next = root; // 默认的next指针:root
for (TrieNode<numChar>* q = front->next; q != NULL; q = q->next) {
if (q->sons[i] != NULL) {
p->next = q->sons[i];
if (p->next->dangerous)p->dangerous = 1;
break;
}
}
}
bfs.pop();
}
}
public:
//构造Trie图。
//strs: 若干个模式串。
//nStr:模式串的书香。
//ch2idx:转换函数,将字符转换为它在字符集中的索引。
Trie(char** strs, int nStr, int (*ch2idx)(char)): ch2idx(ch2idx)
{
root = new TrieNode<numChar>();
for (int i = 0; i < nStr; i++) AddStr(strs[i], this->ch2idx);
Build();
}
~Trie(){ delete root; }
// 和字符串str匹配。如果str含有某个模式串,返回true。
bool Match(char* str)
{
TrieNode<numChar>* node = root;
for (int i = 0; str[i] != '\0';) {
if (node->dangerous) return true;
int idx = ch2idx(str[i]);
if (node->sons[idx] != NULL) { //该字符成功匹配
node = node->sons[idx];
i++;
}
else { // 该字符失配
if (node != root) node = node->next; // 不在根节点,到next继续匹配
else i++; // 在根节点,继续下一个字符匹配
}
}
return root->dangerous; // 它为真当且仅当空串为一个模式串。这是为了正确处理str也是空串的情况。
}
};
模板应用示例
int main()
{
int nMod;
scanf("%d", &nMod);
char** strs = new char*[nMod];
for (int i = 0; i < nMod; i++) {
strs[i] = new char[121];
scanf("%s", strs[i]);
}
Trie<26>* triemap = new Trie<26>(strs, nMod, [](char x) {return x - 'a'; });
for (int i = 0; i < nMod; i++)
delete[] strs[i];
delete[] strs;
int nTest;
char* buf = new char[1001];
scanf("%d", &nTest);
while (nTest--) {
scanf("%s", buf);
if (triemap->Match(buf)) printf("YES\n");
else printf("NO\n");
}
delete[] buf;
return 0;
}