预备知识:kmp算法,字典树
debug了好多天,才终于出正确结果了,终于知道逻辑错误的可怕了,越来越理解什么叫做“比编译错误更折磨人的是逻辑错误”。
具体实现就看注释了。。。。。。
#include<cstdio>
#include<string>
#include<queue>
#include<iostream>
using namespace std;
struct node
{
char c;//该节点所代表的值,其实不加也可以,因为字典树的每一层就是a-z的字典序
int end;//是否为某个单词的结尾
int num;//以该节点,为结尾的单词个数
node *n[26];//字典树指针
node *fail;//fail指针
char *str;//若该节点是单词结尾,要在此处存入单词,输出统计结果时用
node()//初始化节点
{
end = 0;
num = 0;
fail = NULL;
for (int i = 0; i < 26; i++)
{
n[i] = NULL;
}
}
};
queue<node*> endnode;//单词结尾的节点组成的队列,用于最后输出统计结果
void insert(node *root, char *c)//字典树中插入字符串
{
node *op = root;//将要插入的节点的父节点指针
node *p;//新建节点时使用的操做指针
int x;//存储字符在子结点的下标
for (int i = 0; i < strlen(c); i++)
{
x = c[i] - 'a';
if (op->n[x] == NULL)//若没有该节点,则分配空间新建节点
{
p = (node *)malloc(sizeof(node));
//对新节点赋值
p->fail = NULL;
p->end = 0;
p->c = c[i];
p->num = 0;
//子节点指空
for (int op = 0; op < 26; op++)
{
p->n[op] = NULL;
}
op->n[x] = p;//将新节点连入字典树
}
if (i == strlen(c) - 1)//当处理到字符串最后一位时,将以此位结尾的单词数目加一
{
op->n[x]->end++;
op->n[x]->str = c;
endnode.push(op->n[x]);
}
op = op->n[x];//为处理下一个做准备
}
}
void build_fail(node *root)//建立fail指针,利用队列(FIFO),对字典树广度优先搜索
{
node *op,*findfail;
//op为要建立fail指针的节点的父指针
//findfail永远指向可能是要找的节点的父节点
queue<node*> k;
op = root;
//k.push(op);
//对root下的第一层做特殊处理,fail指向root
for (int i = 0; i < 26; i++)
{
if (op->n[i] != NULL)
{
op->n[i]->fail = root;
k.push(op->n[i]);
}
}
while (!k.empty())
{
op = k.front();//先提取元素,再出队列
k.pop();
findfail = op->fail;
for (int i = 0; i < 26; i++)
{
if (op->n[i] != NULL)
{
k.push(op->n[i]);//新元素入队
if (findfail->n[i] != NULL)
{
op->n[i]->fail = findfail->n[i];//op.fail.n[i]存在,则直接建立fail指针
}
else//fail指针不存在
{
//当findfail追到第二层时,该节点的n[i].fail就是root了。
if (findfail->fail = root)
op->n[i]->fail = root;
else//让findfail一级一级找它的fail指针,当然提前要做判断
findfail = findfail->fail;
}
}
}
}
}
void ac_automachine(node *root,char *c)
{
node *op=root;
for (int i = 0; i < strlen(c); i++)//对文本处理
{
int x = c[i] - 'a';
if (op->n[x] != NULL)
{
if (op->n[x]->end > 0)
{
op->n[x]->num++;//判断下是否是单词结尾,是,就++
}
op = op->n[x];
continue;
}
else//当不存在时就跳转到fail指针所指向的位置
{
while (op != root)//到root就可以截至
{
op = op->fail;//沿着fail指针一直延续下去
if (op->n[x] != NULL)
{
if (op->end > 0) op->num++;
op = op->n[x];
if (op->end > 0) op->num++;
break;
}
}
}
}
}
void output()
{
while (!endnode.empty())
{
node *op = endnode.front();
endnode.pop();
printf("%s的个数:%d\n", op->str, op->num);
}
}
int main()
{
node a;
a.c = '&';
char c[100] = "sayhgbrherbrbrbrhbrh";
//scanf("%s", c);
insert(&a, "say");
insert(&a, "brh");
insert(&a, "she");
insert(&a, "her");
insert(&a, "he");
build_fail(&a);
ac_automachine(&a, c);
output();
return 0;
}