字典(trie)树的应用与实现

字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

常见的可以使用字典树解决的问题举例:

1、已知N个单词,对于每一个单词,判断它有没有出现过,如果出现了,求第一次出现在第几个位置。

2、已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。

3、有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M,返回频数最高的100个词。

4、1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。

5、一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词。

6、给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出。

        上面的问题都可以通过建一个字典树解决,将字符串分别插入到字典树中,插入的同时可以进行查询,这样就可以知道该字符串有没有出现过。字典树结点可以添加一些附加信息,比如该结点代表的字符出现的次数,该结点是不是某个字符串的结尾以及该字符串出现的次数等。另外,对字典树进行先序遍历可以输出字符串的排序。

Tire树的结构如下图所示:

每个节点代表一个字符,从根节点向下遍历可以得到所有字符串的前缀,遇到结尾字符(红色)就得到一个字符串。

下面是我写的字典树简单实现,该字典树不仅保存了每个字符出现的次数(成员变量_count),该字典树中字符串的出现次数(成员变量_isEnd)。在插入字符串的每个字符的时候需要更新_count,插入完一个字符串后更新_isEnd。最后,删除一个字符串需要注意的是先判断是否存在待删除的字符串,如果存在,那么应该从下而上判断和删除节点(如果节点的计数为1,那么就释放内存,否则减小_count)。

/*实现一个字典树*/
#include <iostream>
#include <cstring>
#include <stack>



struct TrieNode
{
	enum { MAX = 26 };
	int _isEnd;//表示字符串的结尾,_isEnd可以表示字符出现次数
	int _count;//字符出现的次数
	TireNode* children[MAX];//指针数组
	TireNode() : _isEnd(0), _count(1)
	{
		memset(children, NULL, sizeof(children));
	}
};

TrieNode* create()
{
	return new TireNode;
}

void insert(TrieNode* root, const char* str)
{
	if (NULL == root || NULL == str || '\0' == *str)
	{
		return;
	}

	TrieNode* p = root;
	while (*str != '\0')
	{
		if (NULL == p->children[*str - 'a']) //结点不存在
		{
			p->children[*str - 'a'] = new TrieNode;
		}
		else
		{
			p->children[*str - 'a']->_count++;//更新字符结点计数
		}

		p = p->children[*str - 'a'];
		++str;
	}
	p->_isEnd++;//插完一个字符串更新其出现的次数
}

//字典树root是否包含字符串str, 如果isPrefix == true,那么str是root中某个字符串的一个前缀
bool contains(TrieNode* root, const char* str, bool& isPrefix)
{
	if (NULL == root || NULL == str)
	{
		return false;
	}
	if ('\0' == *str)
	{
		return true;
	}

	TrieNode* p = root;

	while (p != NULL && *str != '\0')
	{
		p = p->children[*str - 'a']; //p指向str的当前字符
		++str;//str跳到下一个字符
	}
	//当*str == '\0'的时候,p指向str的最后一个有效字符(不是'\0')
	isPrefix = false;
	if (p != NULL)
	{
		isPrefix = true;
		if (p->_isEnd > 0)//字符串结束符标记
		{
			return true;
		}
	}
	return false;
}

//从字典树中删除字符串str,如果不存在就返回false,成功删除就返回true
bool remove(TrieNode* root, const char* str)
{
	if (NULL == root || NULL == str)
	{
		return false;
	}
	if ('\0' == *str)
	{
		return true;
	}

	std::stack<TrieNode**> s;//结点只能从下向上删除,否则会破坏树结构,释放内存后要置NULL,故保存指针的地址!!!
	TrieNode* p = root;
	while (p != NULL && *str != '\0')
	{
		if ((p->children[*str - 'a']) != NULL)
		{
			s.push(&(p->children[*str - 'a']));//压入指针地址,很重要!
		}
		
		p = p->children[*str - 'a'];
		++str;//str跳到下一个字符
	}
	
	if (p != NULL && p->_isEnd > 0) //存在字符串str
	{
		TrieNode** end = s.top();//代表待删除的字符串的最后一个字符的节点
		(*end)->_isEnd--;
		while (!s.empty())
		{
			TrieNode** toDelete = s.top();
			if ((*toDelete)->_count == 1)//计数器为1了释放内存,否则只更新计数器
			{
				delete *toDelete;
				*toDelete = NULL;
			}
			else
			{
				(*toDelete)->_count--;
			}
			s.pop();
		}
	}
	
	return false;//trie树中不存在str
}

void release(TrieNode* root)
{
	for (int i = 0; i < TrieNode::MAX; ++i)
	{
		if (root->children[i] != NULL)
		{
			release(root->children[i]);
		}
	}
	delete root;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值