Trie树

Trie树又叫字典树、前缀树。经常用于字符串查找、字符串前缀匹配。

先来看一个Tire树

Trie树_第1张图片

这个Trie树中存有字符串ab,abc,bd,dda。根结点没有信息,根结点有26个指针(总共有26个字母,为空的没标出来),顺着指针走,遇到红色结点,根结点到红色结点的路径便组成一个字符串。字符串的公共前缀是公用一条路径。

Trie树占用了大量空间来提高查找效率,每个结点都有26个指针。

TrieNode结点中,要有26个指针对应26个字母,还有值,判断这个结点是不是一个单词的终结点。所以结点定义如下:

struct TrieNode{
	bool isword;// 是否是一个单词的终结点
	TrieNode *next[26];//子树指针
	TrieNode():isword(false){
		for(int i=0;i<26;i++)
		next[i]=NULL;
	}
};

对于Trie树,要有基本的操作:插入,查找和销毁。

class TrieTree{
	public:
	TrieTree()
	{
		root=new TrieNode();
	}
	~TrieTree()
	{
		destroy(root);
	}
	void insert(const char *s);//插入
	bool find(const char *s);//查找
	void destroy(TrieNode *r);
	private:
	TrieNode *root;
};
//s指向字符串
void TrieTree::insert(const char *s)
{
	TrieNode *r=root;
	while(*s)
	{
		if(!r->next[*s-'a'])//子树指针为空
		{
			TrieNode *t=new TrieNode();//创建子树结点
			r->next[*s-'a']=t;
		}
		r=r->next[*s-'a'];
		s++;
	}
	r->isword=true;//对应为红色
}
	//是否存在单词s
bool TrieTree::find(const char *s)
{
	TrieNode *r=root;
	while(*s)
	{
		if(!r->next[*s-'a'])
		return false;
		r=r->next[*s-'a'];
		s++;
	}
	return r->isword;
}
void TrieTree::destroy(TrieNode *r)
{
	for(int i=0;i<26;i++)
	{
		if(!r->next[i])
			destroy(r->next[i]);
	}
	delete r;
}
完成后可以测试一下:

int main()
{
	char *s1="ab";
	char *s2="abc";
	char *s3="bd";
	char *s4="dda";

	TrieTree T;
	T.insert(s1);
	T.insert(s2);
	T.insert(s3);
	T.insert(s4);
	bool test=T.find("dda");
	test=T.find("dda");
	test=T.find("ab");

	return 0;
}


对于TrieTree删除一个单词,虽然比较少用,但是还是值得思考。

TrieTree是单向的,所以删除一个结点时,不仅要释放这个结点,还要修改其父指针。而删除过程是:

1、把这个单词最后一个字母对应的结点isword改为FALSE。

2、判断这个结点用不用删除,无孩子则删除,并修改其父结点对应的指针域为NULL,转步骤3;否则保留,结束。

3、判断其父节点要不要删除,isword为FALSE且无孩子则删除,并以此判断其父节点,直到根结点。



可以看出是先修改子结点,再修改父结点,这样的话,有两种解决方法:

1用递归,可以参考单向链表的递归反转

2用堆栈


对于用递归,试了一下。需要添加判断孩子结点个数的函数;因为用到了root结点,也把root成员改为了public。

delWord递归来删除。

//孩子个数
int TrieTree::childNum(TrieNode *r)
{
	int num=0;
	for(int i=0;i<26;i++)
	{
		if(r->next[i]!=NULL)
			num++;
	}
	return num;
}
void TrieTree::delWord(TrieNode *r,const char *s)
{
	if(!*(s+1))//是不是最后一个结点的父结点
	{
		r->next[*s-'a']->isword=false;//修改isword,其已不是单词结尾结点
		if(childNum(r->next[*s-'a'])==0)//最后的这个结点可以删除
		{
			delete r->next[*s-'a'];
			r->next[*s-'a']=NULL;
			return ;
		}
		return ;
	}
	const char *t=s;
	t++;
	delWord(r->next[*s-'a'],t);//递归
	if(r->next[*s-'a']->isword==false&&childNum(r->next[*s-'a'])==0)
	{
		delete r->next[*s-'a'];
		r->next[*s-'a']=NULL;
	}
}





你可能感兴趣的:(Trie树)