Trie树又叫字典树、前缀树。经常用于字符串查找、字符串前缀匹配。
先来看一个Tire树
这个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; } };
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; } }