hihocoder#1014之trie树

描述

小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,

在编程的学习道路上一同前进。

这一天,他们遇到了一本词典,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能对于每一个

我给出的字符串,都在这个词典里面找到以这个字符串开头的所有单词呢?

身经百战的小Ho答道:“怎么会不能呢!你每给我一个字符串,我就依次遍历词典里的所有单词,检查你

给我的字符串是不是这个单词的前缀不就是了?

小Hi笑道:“你啊,还是太年轻了!假设这本词典里有10万个单词,我询问你1万次,你得要算到哪年哪月去?”

小Ho低头算了一算,看着那一堆堆的0,顿时感觉自己这辈子都要花在上面了…

小Hi看着小Ho的囧样,也是继续笑道:“让我来提高一下你的知识水平吧~你知道树这样一种数据结构么?”

小Ho想了想,说道:“知道~它是一种基础的数据结构,就像这里说的一样!”

小Hi满意的点了点头,说道:“那你知道我怎么样用一棵树来表示整个词典么?”

小Ho摇摇头表示自己不清楚。

提示一:Trie树的建立

小Hi于是在纸上画了一会,递给小Ho,道:“你看这棵树和这个词典有什么关系?”

《hihocoder#1014之trie树》

小Ho盯着手里的纸想了一会道:“我知道了!对于从树的根节点走到每一个黑色节点所经过的路径,

如果将路径上的字母都连起来的话,就都对应着词典中的一个单词呢!

小Hi说道:“那你知道如何根据一个词典构建这样一棵树么?”

“不造!”

“想你也不知道,我来告诉你吧~”小Hi摆出一副老师的样子,说道:“你先这么想,如果我已经有了这样

的一个词典和对应的一棵树,我要添加一个新的单词apart,我应该怎么做?”

“让我想想……”小Ho又开始苦思冥想:“首先我要先看看已经能走到哪一步了对吧?比如我从1号节点

走”a”这一条边就可以走到2号节点,然后从2号节点走”p”这一条边可以走到3号节点,然后…就没路可走了!

这时候我就需要添加一条从3号节点出发且标记为”p”的边才可以接着往下走…最后就是这样了!然后把最后

到达的这个结点标记为黑色就可以了。”

《hihocoder#1014之trie树》

小Hi说道:“真聪明~那你不妨再算算如果是一个有10W个单词的词典,每个单词的长度不超过10的话,

这棵树会有多大?”

小Ho于是掏出笔来,一边画一遍念叨:“假设我已经将前三个单词构成了这样一棵树,那么我要添加一个

新的单词的时候,最坏情况是这个单词和之前的三个单词都没有公共前缀,那么这个新的单词的长度如果

是5的话,我就至少要添加5个结点到树中才能够继续表示这个词典!”

“而如果每次都是最坏情况,这棵树最多也就100W个结点这么大!更何况最坏情况是不可能次次发生的!

毕竟字母表也才26个字母呢!”小Ho继续说道。

“嗯~这样我们是不是就可以用(单词个数*单词长度)个结点来表示一个词典了呢?小Hi问道。

“是的呢!”小Ho道:“但是这样一棵树又有什么用呢?”

“可别小看了它,它就是传说中的Trie树哦~至于他有什么用,一会你就知道了!”小Hi笑嘻嘻的回答道。

提示二:如何使用Trie树

小Hi在树上用绿色标出了一个节点,递给小Ho。

《hihocoder#1014之trie树》

“这个结点是从根节点先走”a”然后走”p”到达的结点呢!我知道了,以这个结点为根的子树里

标记结点都是以”ap”为前缀的单词!而且所有以”ap”为前缀的单词都在以这个节点为根的子树里。”

“是的呢~那你对怎么解决我的问题有想法了么?”小Hi追问道。

“那就是每次拿到字符串之后,我在树上找到其对应的那个结点,然后统计这个节点中有多少个

标记节点?”

小Ho不是很确定的答道:“但是这样…似乎在最坏情况,也就是你每次给个字符串都很短的时候,

我还是要扫描这棵树的很大一部分呢?也就是说虽然平均时间复杂度降低了,但最坏情况

时间复杂度还是很高!”

小Hi笑嘻嘻道:”没想到你自己看出来了呢~我还以为又要教训你了!那你有什么好的解决方法么?”

“没呢!小Hi你就别卖关子了,赶紧告诉我吧!”被折磨的够呛的小Ho开始求饶。

“好吧!就帮你这一回~”

提示三:在建立Trie树时同时进行统计!

“小Ho你有没有想过这样一个问题?不妨称以T为根的子树中标记节点的个数为L[T],既然我要

统计某个L[T1],而这个结点是不确定的,我有没有办法一次性把所有结点的L[T]求出来呢?

“似乎是有的,老师以前说过,递归什么的…”小Ho答道。

“递归太复杂了!你这么想,在构建Trie树的时候,当经过一个结点的时候,说明什么?”

“我想想,经过一个结点…标记结点…说明以这个结点为根的子树中将要多出一个标记结点?”

“没错!那你有没有什么办法来记录这个改变呢?”

“我想想,最开始置所有L[T]=0,然后每次添加一个新单词的时候,都将它经过的所有结点的

L[T]全部+1,这样我构建完这棵Trie树的时候,我也就能够同时统计到所有L[T]了,对么?”

输入

输入的第一行为一个正整数n,表示词典的大小,其后n行,每一行一个单词(不保证是

英文单词,也有可能是火星文单词哦),单词由不超过10个的小写英文字母组成,可能存在

相同的单词,此时应将其视作不同的单词。接下来的一行为一个正整数m,表示小Hi询问的

次数,其后m行,每一行一个字符串,该字符串由不超过10个的小写英文字母组成,表示

小Hi的一个询问。

在20%的数据中n, m<=10,词典的字母表大小<=2.

在60%的数据中n, m<=1000,词典的字母表大小<=5.

在100%的数据中n, m<=100000,词典的字母表大小<=26.

输出

对于小Hi的每一个询问,输出一个整数表示词典中以小Hi给出的字符串为前缀的单词的个数。

样例输入

5
babaab
babbbaaaa
abba
aaaaabaa
babaababb
5
babb
baabaaa
bab
bb
bbabbaab

样例输出

1
0
3
0
0

#include
#include
#include

struct node
{
	int cnt;//存放该节点之前所有字符都相同的个数
	struct node* alph[26];//alph数组存放下一个节点的地址
};

void init(node** p)//必须用双星,node*表示类型,*p用于接收地址
{
	*p = (node*)malloc(sizeof(node));
	(*p)->cnt = 0;
	for(int i = 0; i < 26; i++)
		(*p)->alph[i] = NULL;
}

void insert(node* p1, char s[])
{
	node* p2 = NULL;
	for(int i = 0; i < strlen(s); i++)
	{
		if(p1->alph[s[i] - 'a'] == NULL)
		{
			init(&p2);
			p1->alph[s[i] - 'a'] = p2;//把新建的p2地址赋给p1中的指针数组
			p2->cnt++;
			p1 = p2;
		}
		else
		{
			p1 = p1->alph[s[i] - 'a'];//指向下一节点
			p1->cnt++;
		}
	}
}

int search(node* p, char s[])
{
	int i;
	for(i = 0; i < strlen(s); i++)
	{
		if(p->alph[s[i] - 'a'] == NULL)
			break;
		else
			p = p->alph[s[i] - 'a'];
	}
	if(i == strlen(s))
		return p->cnt;//表示找到前缀的最后一个字符,返回个数
	else
		return 0;
}

int main(void)
{
	int n, m;
	char s[11];
	node *pnode = NULL;
	init(&pnode);//一定要加取址符,否则pnode的值仍为NULL
	while(scanf("%d", &n) != EOF)
	{
		while(n--)
		{
			scanf("%s", s);
			insert(pnode, s);
		}
		scanf("%d", &m);
		while(m--)
		{
			scanf("%s", s);
			printf("%d\n", search(pnode, s));
		}
	}
	//system("pause");
}

    原文作者:Trie树
    原文地址: https://blog.csdn.net/Q1204265228/article/details/41702181
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注