算法——利用Trie树统计某种前缀的单词的个数

题目描述:向词典中添加单词(几次同样的输入当做不同的单词,这里默认英文单词)。然后进行查询单词的任务,输入一个单词前缀,则要输出以该单词为前缀的单词个数。
程序要求:先输入字典的单词个数,并以此输入这些单词。再输入要查询的单词前缀个数,并且输入这些单词。依次输出查询得出的单词前缀对应的单词个数。


输入样例:

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

输出样例:

1
0
3
0
0




我的想法:这个问题如果使用蛮力法,则需要每查询一个单词,就遍历一次字典里的每一个单词,并且每个单词要一个个字母进行匹配。需要O(n^2)。对于大量单词来说是不现实的做法。故而引入了Trie树,这种树又被称作字典树,来解决这种问题。上图:


《算法——利用Trie树统计某种前缀的单词的个数》


画的比较简陋,但能够说明问题,根节点是一个空节点。ok,让我们看一看这棵树是怎么来的。。过程如下,假设我们手头有这样几个单词:a,as,ae,br,c。一共五个单词。我们建立了一颗空的TrieTree树,只有一个root指针指着根节点。我们向字典中添加单词a,即向TrieTree中添加一个单词a。我们从root出发,看它是否有叫a的子节点。发现没有,则从根节点拉出一个指针指向新建立的a节点。这样,单词a就添加进来了。
下一步,要将单词as添加进字典中,从根节点出发,扫描单词as,第一个字母a,根节点有a,扫描第二个字母s,a节点没有s子节点,创建一个s节点,当a的子节点,a拉出个指针指向s节点。as就添加进去了。。。依次类推。
好了,,,看似一切都很美好。我们先来看看这里有什么问题。。。我要查询单词a,为前缀的单词,请问有几个?答案是3个,但是从图中很难判断,可以肯定的是我们的添加方法中,as,ae肯定是两个单词,但是a是不是单词很难讲。现在我们按照刚才的方法添加一个单词ass,,,,,,==。

《算法——利用Trie树统计某种前缀的单词的个数》


请问as还是不是单词了,,,很难看出来啊,ass,ae肯定是,但是a,as都变得无从考察了。我们只好在每个节点中添加一个成员变量:isword,表明到这个节点为止,是否能够够成一个单词。故a,s,s,e,r,c几个节点的isword值都为true,而b的isword值为false。


至此,我们就可以在这棵TrieTree树中进行单词的查找了。可以发现,同样的前缀在这棵树中是被公用的。故我们查询一个单词前缀,不用每次遍历字典中的一个个单词,直接在书中按图索骥就行了,比如说我们找a前缀的单词,直接从根节点跑到a节点,然后数一数以a节点为根的字数中有多少个单词即可。。。
但是这依旧有一个很严重的问题。即我们要在a节点上进行每个节点的遍历,这显然是很没效率的。
解决方案是在创建TrieTree的同时将整个前缀单词数这个信息给顺带统计了。。我们为TrieNode添加一个新的成员变量:wordcount。这个变量表示经过这个节点所添加的单词数,换一种说法:以根节点到该节点的路径组成的字符串为单词前缀,一共有多少个单词。这样,我们在添加一个时,,,假设还是刚刚添加ass这个单词的例子,扫描根节点,先给root指向的根节点的wordcount++,再到a,给a的wordcount++。再給s的wordcount++。最后,第二个s节点被创建出来,自己的wordcount原始值为0,自然也要佳佳,因为ass为前缀的单词此时为1个,就是ass本身。


问题已经说明了,还有细节有待coder关注的细节可能就是相同输入默认相同单词怎么办?有火星字符怎么办?要查询出某个相同单词有几个怎么办?但这些都不影响TrieTree的主要思路了,是特定的细节。
下面是代码,C++基础不好,写的有很多不规范请大家指出来并且给我些建议,析构函数啊什么的,还有那个节点池有没有什么明显更好的替代方法等,我下阶段一定要好好研究下C了。。。


<span style="font-size:18px;">// Hiho_trietree.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "iostream"
#include "string"
#define MAX_CHARSET 26	//字符集大小为26个英文字母
#define POOLSIZE 1000000000	//节点池的容量
using namespace std;

//节点的结构体
struct TrieNode
{
	int wordcount;	//以该节点为前缀的单词数量
	TrieNode* childNode[MAX_CHARSET];	//26个指针
	bool isword;	//表示该节点是某个单词的结尾字母
};

TrieNode nodepool[POOLSIZE];	//节点池
int poolindex=0;	//节点池的浮标

//这是建立的Trie树
class TrieTree
{
public: TrieTree();
		~TrieTree();
		void insertWord(const char word[]);	//参数是传进来的字符串和该字符串的长度
		TrieNode* createNode();	//创建节点
		void deleteNode(TrieNode *p);
		int searchWord(const char word[]);	//查询参数指定串为前缀的单词数量
private:
	TrieNode *root;	//树根节点,里面没有节点值
};

//构造器
TrieTree::TrieTree()
{
	root=createNode();
}

//析构函数
TrieTree::~TrieTree()
{
	deleteNode(root);
}

//删除Trie树中的节点
void TrieTree::deleteNode(TrieNode *p)
{
	if(!p)
	{
		return;
	}
	for(int i=0;i<MAX_CHARSET;i++)
	{
		if(p->childNode[i])
		{
			deleteNode(p->childNode[i]);
		}
	}
	p=NULL;
}

//创建节点,从节点池中取出一个节点来用
TrieNode* TrieTree::createNode()
{
	if(poolindex>=POOLSIZE)	//节点池已经耗光了
	{
		return NULL;
	}
	nodepool[poolindex].wordcount=0;	//一开始没有数量
	for(int i=0;i<MAX_CHARSET;i++)
	{
		nodepool[poolindex].childNode[i]=NULL;
	}
	isword=false;	//默认该节点不是某单词的结尾
	return &nodepool[poolindex++];
}

//在树中插入一个单词,n为长度
void TrieTree::insertWord(const char word[])
{
	if(word!=NULL)
	{
		TrieNode *p=root;
		int i=0;
		while(word[i])
		{
			int lettervalue=word[i]-'a';	//word[i]='b',则lettervalue=1
			if(lettervalue>=26||lettervalue<0)	//包含了非英文单词的输入
			{
				return;
			}
			if(p)
			{				
				if(p->childNode[lettervalue])	//已经有过该节点创建了
				{
					p=p->childNode[lettervalue];	//p指针下移
					p->wordcount++;
				}
				else	//创建该节点
				{
					//创建一个节点
					p->childNode[lettervalue]=createNode();
					if(p->childNode[lettervalue])
					{
						p=p->childNode[lettervalue];
						p->wordcount++;		//单词数加1
					}
				}
			}
			i++;
		}
		//要插入的单词已经添加好了,此时需要将该路径的最后一个节点标为
		//isword=true
		p->isword=true;
	}

}

int TrieTree::searchWord(const char word[])
{
	if(!word)
	{
		return 0;
	}
	TrieNode *p=root;	//先指着根节点
	int count=0;
	if(p)
	{
		int i=0;
		while(word[i])
		{
			int lettervalue=word[i]-'a';
			p=p->childNode[lettervalue];	//指针下移
			if(!p)	//p已经到了尽头
			{
				return 0;
			}
			count=p->wordcount;
			i++;	
		}
		return count;
	}
	return 0;
}

int main(void)
{
	TrieTree tree=TrieTree();
	tree.insertWord("babaab");
	tree.insertWord("babbbaaaa");
	tree.insertWord("abba");
	tree.insertWord("aaaaabaa");
	tree.insertWord("babaababb");
	int result=tree.searchWord("babb");
	cout<<"the search result:"<<result<<endl;
	result=tree.searchWord("baabaaa");
	cout<<"the search result:"<<result<<endl;
	result=tree.searchWord("bab");
	cout<<"the search result:"<<result<<endl;
	result=tree.searchWord("bb");
	cout<<"the search result:"<<result<<endl;
	result=tree.searchWord("bbabbaab");
	cout<<"the search result:"<<result<<endl;

	/*int n=0;
	int i=0;
	TrieTree tree=TrieTree();
//	cout<<"输入字典单词的单词量:";
	cin>>n;
	for(i=0;i<n;i++)
	{
		//cout<<"输入第"<<i+1<<"个单词:";
		string word;
		cin>>word;
		const char *p=word.data();
		tree.insertWord(p);
	}
	//cout<<"字典输入完毕!"<<endl<<"请输入要查找的单词总数:";
	cin>>n;
	int *result=new int[5];
	for(i=0;i<n;i++)
	{
		//cout<<"输入第"<<i<<"个要查询的单词:";
		string word;
		cin>>word;
		const char *p=word.data();
		result[i]=tree.searchWord(p);
	}
	//输出结果
	for(i=0;i<n;i++)
	{
		cout<<result[i]<<endl;
	}
	delete result;*/	
	return 0;
}
</span>


如发现谬误,请提出来交流。

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

发表评论

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