作者:myjoying
时间:2012年9月10日
在我的前一篇博文《AC算法--多模式匹配(论文解析版)》(http://blog.csdn.net/myjoying/article/details/7960534)中介绍了AC算法的实现原理,但是并没有给出AC算法的具体实现。AC算法的实现必须首先解决几个问题。首先需要解决的是goto表的实现问题。
由前面的分析可知,goto表本质上是一个有限状态机。存储goto表的方式有很多,例如用二维数组和链表等。但是数组的方式势必造成空间的极大浪费,链表会造成查询效率的降低。综合考虑存储空间和运行效率,在实际应用中goto表一般是采用trie树的形式进行存储。
在计算机科学中中,trie,又称前缀树,字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
#include <iostream>
#include<cstdlib>
#define MAX 26
using namespace std;
typedef struct TrieNode //Trie结点声明
{
bool isStr; //标记该结点处是否构成单词
struct TrieNode *next[MAX]; //儿子分支
}Trie;
void insert(Trie *root,const char *s) //将单词s插入到字典树中
{
if(root==NULL||*s=='\0')
return;
int i;
Trie *p=root;
while(*s!='\0')
{
if(p->next[*s-'a']==NULL) //如果不存在,则建立结点
{
Trie *temp=(Trie *)malloc(sizeof(Trie));
for(i=0;i<MAX;i++)
{
temp->next[i]=NULL;
}
temp->isStr=false;
p->next[*s-'a']=temp;
p=p->next[*s-'a'];
}
else
{
p=p->next[*s-'a'];
}
s++;
}
p->isStr=true; //单词结束的地方标记此处可以构成一个单词
}
int search(Trie *root,const char *s) //查找某个单词是否已经存在
{
Trie *p=root;
while(p!=NULL&&*s!='\0')
{
p=p->next[*s-'a'];
s++;
}
return (p!=NULL&&p->isStr==true); //在单词结束处的标记为true时,单词才存在
}
void del(Trie *root) //释放整个字典树占的堆区空间
{
int i;
for(i=0;i<MAX;i++)
{
if(root->next[i]!=NULL)
{
del(root->next[i]);
}
}
free(root);
}
int main(int argc, char *argv[])
{
int i;
int n,m; //n为建立Trie树输入的单词数,m为要查找的单词数
char s[100];
Trie *root= (Trie *)malloc(sizeof(Trie));
for(i=0;i<MAX;i++)
{
root->next[i]=NULL;
}
root->isStr=false;
scanf("%d",&n);
getchar();
for(i=0;i<n;i++) //先建立字典树
{
scanf("%s",s);
insert(root,s);
}
while(scanf("%d",&m)!=EOF)
{
for(i=0;i<m;i++) //查找
{
scanf("%s",s);
if(search(root,s)==1)
printf("YES\n");
else
printf("NO\n");
}
printf("\n");
}
del(root); //释放空间很重要
return 0;
}
当然,Trie树除了应用在AC算法中外,还有很多其他的应用。
Trie树的应用包括:
(1) 存储字典;
(2) 应用在最大匹配算法,例如拼写检查,断词等;
(3) 搜索引擎中的词频统计;
(4) 树中分支的先序遍历结果就是字典序的;