Visitors hit counter dreamweaver

Tire树

     今天把Trie树彻底的看了下。发现网上有两篇非常好的文章,通过他们的博客,我对Trie树有了大题的了解。并且通过理解 消化 综合他们的知识,再结合我自己的编程爱好,我也把具体的程序实现了一遍,这样能对Trie树有更加深刻的认识!

 他们是:勇幸|Thinking   和 Maik 。 感谢他们。 下面的分析也是从他们的博客摘抄以便理解的。

  • Trie原理

Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

  • Trie性质

好多人说trie的根节点不包含任何字符信息,我所习惯的trie根节点却是包含信息的,而且认为这样也方便,下面说一下它的性质

1.    字符的种数决定每个节点的出度,即branch数组(空间换时间思想)

2.    branch数组的下标代表字符相对于a的相对位置

3.    采用标记的方法确定是否为字符串。

4.    插入、查找的复杂度均为O(len),len为字符串长度

 

 

      其基本操作有:查找 插入和删除(删除全部和删除单个单词)

 

搜索字典项目的方法为:

 

(1) 从根结点开始一次搜索;

 

(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;

 

(3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。

 

(4) 迭代过程……

 

(5) 在某个结点处,关键词的所有字母已被

 

  • Trie的示意图

如图所示,该trie树存有abc、d、da、dda四个字符串,如果是字符串会在节点的尾部进行标记。没有后续字符的branch分支指向NULL

 

  • Trie的优点举例

已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。下面对比3种方法:

1.    最容易想到的:即从字符串集中从头往后搜,看每个字符串是否为字符串集中某个字符串的前缀,复杂度为O(n^2)。

2.    使用hash:我们用hash存下所有字符串的所有的前缀子串。建立存有子串hash的复杂度为O(n*len)。查询的复杂度为O(n)* O(1)= O(n)。

3.    使用trie:因为当查询如字符串abc是否为某个字符串的前缀时,显然以b,c,d....等不是以a开头的字符串就不用查找了。所以建立trie的复杂度为O(n*len),而建立+查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程,hash就不能实现这个功能。所以总的复杂度为O(n*len),实际查询的复杂度只是O(len)。

 

       Trie树是一种非常重要的数据结构,它在信息检索,字符串匹配等领域有广泛的应用,同时,它也是很多算法和复杂数据结构的基础,如后缀树,AC自动机等,因此,掌握Trie树这种数据结构,对于一名IT人员,显得非常基础且必要!(摘抄自:http://dongxicheng.org/structure/trietree/

 

 

#include <iostream>
#include <stack>

using namespace std;

const int sonnum=26, base='a';

struct Trie{
      int num;  //记录有多少个单词能到达次,也即相同前缀的个位
      bool terminal; //判断是否是结束节点
      struct Trie *son[sonnum];
      Trie()
      {
          num=1; terminal=false;
          memset(son,NULL,sizeof(son));
      }
};

Trie* NewTrie()
{
    Trie *temp=new Trie();
    return temp;
}

void Insert(Trie *root, char *s)
{
    Trie *temp=root;
    while(*s)
    {
        if(temp->son[*s-base]==NULL)   //不存在 则建立
                   temp->son[*s-base]=NewTrie();
        else 
temp
->son[*s-base]->num++; temp=temp->son[*s-base]; s++; } temp->terminal=true; //到达尾部,标记一个串 } bool Search(Trie *root, char *s) { Trie *temp=root; while(*s) { if(temp->son[*s-base]!=NULL) temp=temp->son[*s-base]; else return false; s++; } return true; } void DeleteAll(Trie *root) //删除全部节点 { Trie *temp=root; for(int i=0; i<sonnum; i++) { if(root->son[i]!=NULL) DeleteAll(root->son[i]); } delete root; } bool DeleteWord(Trie *root,char *word) //删除某个单词 { Trie *current=root; stack<Trie*> nodes; //用来记录经过的中间节点,供以后自上而下的删除 while(*word && current!=NULL) { nodes.push(current); //经过的中间节点压栈 current=current->son[*word-base]; word++; } if(current && current->terminal) //此时current指向该word对应的最后一个节点 { while(nodes.size()!=0) { char c=*(--word); current=nodes.top()->son[c-base]; //取得当前处理的节点 if(current->num==1) //判断该节点是否只被word用,若不是,则不能删除 { delete current; nodes.top()->son[c-base]=NULL; //把上层的节点next中指向current节点的指针置为NULL nodes.pop(); } else //不能删,只把num相应减1 { current->num--; nodes.pop(); while(nodes.size()!=0) { char *c=--word; current=nodes.top()->son[*c-base]; current->num--; nodes.pop(); } break; } } return true; } else return false; } int main() { Trie *root=NewTrie(); Insert(root,"a"); Insert(root,"abandon"); Insert(root,"abandoned"); //不存在的情况 if(Search(root,"abc")) printf("Found!\n"); else printf("NotFound!\n"); //存在的情况 if(Search(root,"abandon")) printf("Found!\n"); else printf("NotFound!\n"); //能找到 并删除 if(DeleteWord(root,"abandon")) printf("Delete!\n"); else printf("NotFound\n"); //找不到 if(DeleteWord(root,"abc")) printf("Delete!\n"); else printf("NotFound!\n"); return 0; }

 

 

 

 

 

posted @ 2012-04-15 12:21  Jason Damon  阅读(7906)  评论(2编辑  收藏  举报