HihoCoder第四周:Trie图

第四周的题目是前两周的综合,综合在一个是KMP算法的思想,一个是树的这么一个数据结构。

题目 : Trie

输入

每个输入文件有且仅有一组测试数据。

每个测试数据的第一行为一个整数N,表示河蟹词典的大小。

接下来的N行,每一行为一个由小写英文字母组成的河蟹词语。

接下来的一行,为一篇长度不超过M,由小写英文字母组成的文章。

对于60%的数据,所有河蟹词语的长度总和小于10, M<=10

对于80%的数据,所有河蟹词语的长度总和小于10^3, M<=10^3

对于100%的数据,所有河蟹词语的长度总和小于10^6, M<=10^6, N<=1000

输出

对于每组测试数据,输出一行"YES"或者"NO",表示文章中是否含有河蟹词语。

样例输入

6

aaabc

aaac

abcc

ac

bcd

cd

aaaaaaaaaaabaaadaaac

样例输出

YES

一开始我的思路是对每一个节点,每一个struct立面定义一个suffix指针,代表它的后缀节点,但是因为题目要不断地查找后缀节点,这样指针觉得麻烦,所以直接定义一个node[1000005],因为题目中已经说了最多的词语长度就是10^6。

不多说,直接上代码,代码中细说。

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

#define len 1000000

char s[1000006];
char dir[1000006];

int node_count=0;//节点总数

struct Node {
	int flag;//是否结束
	int suffix;//node[p].suffix的值就直接代表它的后缀节点
	int next[26];//这里的next是为构建树所用,即node[p].next['a']就代表当前节点经过字符'a'跳到哪一个节点中去
}node[len];

void init()
{
	int count;
	for(count =0;count<len;count++)
	{
		node[count].flag = 0;
		node[count].suffix = 0;
		for(int i=0;i<26;i++)
			node[count].next[i]= 0;
	}
}

void Add_Trie(char *f_s)
{
	int len_s = strlen(f_s);
	int p=0,i;

	for(i=0;f_s[i];i++)
	{
		if(!node[p].next[f_s[i]-'a'] )
		{
			node[p].next[f_s[i]-'a']= ++node_count;
		}

		p=node[p].next[f_s[i]-'a'];
	}
	node[p].flag = 1;

}

void Cal_Suffix()
{
	queue<int> q;
	/*这个队列我没想到,是看到其他人的代码才想到的
	一开始我是循环了所有节点,结果就是处理的很乱,
	有的处理了两遍。所以,实际上用queue的好处在于
	能理清思路,这个节点进入队列,开始计算这个节点
	后缀节点,顿时思路很清晰明了了。
	其实后缀节点只是为了计算next[]数组时所用的工具。
	因为next数组在树中一开始只是记录真正经过的节点,
	现在要通过next数组来计算如果这时字符串与字典不
	匹配的话,要跳到哪里去,实际上感觉这像是自动机
	的内容*/
	int p, i;
	q.push(0);
	while(!q.empty()) 
	{
		p = q.front();
		q.pop();

		if(node[node[p].suffix].flag ==1)
			node[p].flag = 1;
		for(i = 0; i < 26; i++)
			if(node[p].next[i])//如果该节点有下一个节点
			{
				q.push(node[p].next[i]);//就把它放入到队列中
				if(p)
					node[node[p].next[i]].suffix = node[node[p].suffix].next[i];//如果不是起始节点,那么当前节点P的经过字符i下一个节点的后缀节点是P的后缀节点经过字符i后的节点
			}
			else
				node[p].next[i] = node[node[p].suffix].next[i];//如果没有下一个节点,那么这时suffix发威了,就跳到当前节点P的后缀节点经过字符i的节点上去
	}
}

bool Search(char *f_s)
{
	int len_f_s = strlen(f_s);
	int count1=0,p=0;

	while(f_s[count1])
	{
		p=node[p].next[f_s[count1] - 'a'];
		if(node[p].flag == 1)
		{
			return true;
		}
		count1++;
	}
	return false;
}
int main()
{
	init();

	int dir_count;
	cin>>dir_count;

	while(dir_count--)
	{
		cin>>s;
		Add_Trie(s);//添加到树种
	}

	Cal_Suffix();//计算每个节点的后缀节点

	cin>>dir;
	if(Search(dir))//计算结果
		cout<<"YES"<<endl;
	else
		cout<<"NO"<<endl;

	return 0;
}

整个程序第一点感受就是赋初值别乱赋,想清楚了在开始,一开始的suffix,next数组初值设置为-1,殊不知,可能那里一个取值(数组[-1]),程序就出错了。

第二点感受在memset函数的使用上,以后memset除了0,除了char型数组,用的话要小心谨慎。

第三点感受就是这道题看着复杂,但人家都有hint了。。。理清思路的话,不是很难。

最后一点感受就是 好像是拖得越久,记得越深。。。现在每次做题都能把焦点从算法转移到某一个函数用法或是数据结构上去,说明自己基础还是远远不够。STL中的vector、queue、list等 ,只是知道了其用法,根本没有完全掌握。

下次再编代码之前,要首先理清思路,设计好整个程序的数据结构,各个函数的用法,再去实现其具体功能,比现在这样上来就编然后就提交,再然后就是WA,再去找bug,改得最后面目全非的,思路逻辑混乱,几乎就是看正确答案才能编出来的这幅德行好多了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

原文地址:https://www.cnblogs.com/lightspeedsmallson/p/4785909.html