字典树

简单题: hdu 1251题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1251

待会再分析,下面是代码:

#include<iostream>   
#include<cstring>   
using namespace std;  
const int MAX=26;  
#define CLR(arr,val) memset(arr,val,sizeof(arr))   
class Trie{  
public:  
    Trie(){clear();}//初始化   
    void Insert(Trie *,char *);//插入      
    int Find(Trie *,char *);//查找    
    void clear()  
    {  num=0;  
       CLR(node,0);  
    }  
private:  
    int num;//记录前缀的个数    
    Trie *node[MAX]; //指向儿子结点         
}*root;        
inline void Trie::Insert(Trie *R,char *s)//向以Root为根结点的树中插入字符串s    
{   for(int i=0;i<strlen(s);i++)  
    {  if(!R->node[s[i]-'a'])//若是大写字母改为:s[i]-'A'   
          R->node[s[i]-'a']=new Trie;  
       R=R->node[s[i]-'a'];  
       /**************************************
       例如题目中:banana band bee absolute,分析banana,由于开始都为node[]=0,所以R->node[s[i]-'a']=0,新建Tire类,保存字符b
       出现的次数,R->num++后首字母为b的前缀为1,类推,以"ba"的前缀出现的次数为1,"ban"为1,"bana"为1,"banan"为1,"banana"为1
       ,当输入band时,此时由于开始第一个banana中"b"的num已经为1,所以此时num++=2,说明以"b"为前缀的次数为2,同理:"ba"为1,"b
       an"为2,"band"为1........... 
       **************************************/
       //cout<<R->node[s[i]-'a'];
       R->num++;//访问一次num+1;记录从首结点到该结点的子字符串出现的次数  
        //cout<<R->num<<" ";  
    }  
}  
inline int Trie::Find(Trie *R,char *s)  
{   int i;  
    for(i=0;i<strlen(s)&&R->node[s[i]-'a'];i++)
       R=R->node[s[i]-'a'];//查找满足以字符串s为前缀的字符串个数,由于R是以链表存储的,所以相当于给R赋值操作,知道了R的值,则前缀个数为R->num;  
    if(s[i]=='\0') return R->num;  
    return 0;   
}    
int main()  
{   Trie T;  
    root=new Trie;   
    char s1[50],s2[50];  
    T.clear();  
    while(gets(s1)&&s1[0]!='\0')  
       T.Insert(root,s1);  
    while(gets(s2))  
       cout<<T.Find(root,s2)<<endl;   
    return 0;  
}  

纯粹属于个人言语啊,字典树是一种以空间换时间的做法,根据上例中,Trie会保存所有的前缀,相当于比方说:给你一个字符串不超过十个单词,且输入的字符串不超过,十个字母每位都有26中可能,相当于:26^10,这里使用链表存储,而不是开数组,因为开数组的话一定会说数组超内存,溢出~~换种方式为什么是以空间换时间,在查找的时候我们由于开始保存了每一个前缀出现的次数,所以相当于我吧所有的前缀出现的次数都存在数组中,然后你想知道a[2000]的值,则直接输出a[2000]即可,同样的道理,我们只需知道前缀是什么,就可以直接调用插入语句中的R->num即可,就不会超时了,但由于保存所有的前缀所以需要消耗很大的空间,所以trie是以空间换时间~~

 

下面分析下字典树:字典树的主要作用是查找以某字符串为前缀出现的次数。。。。

性质是:根节点不包含字符,除根节点外每一个节点都只包含一个字符。 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。 每个节点的所有子节点包含的字符都不相同。

字典树查找实现的方法

搜索查找字典项目的方法为:

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

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

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

(4) 迭代过程……

(5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的次数信息,即完成查找。 

至于模板什么的,还是得自己做才行,往往拷贝别人的代码当模板后用到别的地方就感觉不知道该改哪里,不过要是你能看懂别人的代码再加以修改成自己能够看得懂的代码的话也行,反正不管是别人的还是自己的一定得真正弄懂才行~~~

例题2:nyist 163(PhoneList) 题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=163

#include<iostream>   
#include<cstring>   
#include<cstdio>
using namespace std;  
const int MAX=10;  
#define CLR(arr,val) memset(arr,val,sizeof(arr))   
class Trie{  
public:  
    Trie(){clear();} 
    void Insert(Trie *,char *);     
    int Find(Trie *,char *);    
    void clear()  
    {  num=0;  
       CLR(node,0);  
    }  
private:  
    int num;   
    Trie *node[MAX];    
}*root;        
inline void Trie::Insert(Trie *R,char *s)    
{   for(int i=0;i<strlen(s);i++)  
    {  if(!R->node[s[i]-'0']) 
          R->node[s[i]-'0']=new Trie;  
       R=R->node[s[i]-'0'];  
       R->num++;
    }  
}  
inline int Trie::Find(Trie *R,char *s)  
{   int i;  
    for(i=0;i<strlen(s)&&R->node[s[i]-'0'];i++)
       R=R->node[s[i]-'0']; 
    if(s[i]=='\0') return R->num;  
    return 0;   
}    
int main()  
{   Trie T; 
    int k,n;
    char s[100001][10];
    scanf("%d",&k);
    while(k--)
    {  scanf("%d",&n);
       T.clear(); 
       root=new Trie;  
       getchar();
       for(int i=0;i<n;i++)
       {  gets(s[i]);  
          T.Insert(root,s[i]); //直接先将所有的号码插入T中,然后一起查找即可 
       } 
       int flag=0;
       for(int i=0;i<n;i++)
         if(T.Find(root,s[i])>1) flag=1; //记住T.Find(root,s[i])>1,因为本身有一个前缀  
       printf("%s\n",(flag?"NO":"YES"));
    }
    return 0;  
}  

例题3:nyist 290(动物统计)题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=290

#include<iostream>   
#include<cstring>   
#include<cstdio>
using namespace std;  
const int MAX=26;  
#define CLR(arr,val) memset(arr,val,sizeof(arr))   
class Trie{  
public:  
    Trie(){clear();} 
    void Insert(Trie *,char *);     
    int Find(Trie *,char *);    
    void clear()  
    {  num=0;  
       CLR(node,0);  
    }  
private:  
    int num;   
    Trie *node[MAX];    
}*root;        
inline void Trie::Insert(Trie *R,char *s)    
{   for(int i=0;i<strlen(s);i++)  
    {  if(!R->node[s[i]-'a']) 
          R->node[s[i]-'a']=new Trie;  
       R=R->node[s[i]-'a'];  
    }  
    R->num++; //直接保存整个以字符串为前缀的次数 
    //return R->num;//可以删除Find(); 
}  
inline int Trie::Find(Trie *R,char *s)  
{   int i;  
    for(i=0;i<strlen(s)&&R->node[s[i]-'a'];i++)
       R=R->node[s[i]-'a']; 
    if(s[i]=='\0') return R->num;  
    return 0;   
}    
int main()  
{   Trie T; 
    int n,max=0;
    char s[10],s1[10];
    T.clear(); 
    root=new Trie;
    scanf("%d",&n);  
    getchar();
    for(int i=0;i<n;i++)
    {  gets(s);  
       T.Insert(root,s);//插入字符串s 
       if(T.Find(root,s)>max)//一直查找,由于是相同的字符串且需比较的是整个字符串~~ 
       {  strcpy(s1,s);
          max=T.Find(root,s);
       } 
    }
    printf("%s %d\n",s1,max);
    return 0;  
}  

贴下标准程序,写的比较好,反正比我的好的不知道哪里去了罗,呵呵~

#include<iostream> 
#include<cstdio> 
#include<cstring> 
using namespace std; 
template<typename DataType> 
struct TreeNode 
{   DataType data; 
    TreeNode* Next[26]; 
}; 
template<typename DataType,int Max> 
class DicTree { 
public: 
    DicTree(){Top=data;} 
    DataType& operator[](char* str)     
    {   TreeNode<int>* cur=data; 
        char* p= str; 
        while(*p) 
        {   if(Top-data>=Max-100) throw -1; 
            if(cur->Next[*p-'a']==NULL) cur->Next[*p-'a']=++Top; 
            cur=cur->Next[*p-'a']; 
            ++p; 
        } 
        return cur->data; 
    } 
    TreeNode<DataType> data[Max],*Top; 
}; 
DicTree<int,1000000> h; 
int main() 
{   int n,maxn=0; 
    scanf("%d",&n); 
    char str[11],res[11]; 
    while(n--) 
    {   gets(str); 
        int& m=h[str]; 
        ++m; 
        if(maxn <= m) 
        {   maxn = m; 
            strcpy(res,str); 
        }     
    } 
    printf("%s %d",res,maxn); 
    return 0;
}





 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值