字典树入门小结及基础模板详解(HDU1251 HDU1671)

今天开始真正学习字符串相关知识。写了几道字典树入门的题目。第一次总结一下,加深印象。字典树相当于是一种数据结构,每一次从根节点遍历到叶子节点都相当于是查询到这本字典里头的一个单词。节点定义有多种,最简单的只需要一个next数组。(这是以单词为例,所以next数组大小为26,数字的话就只需要10了)

struct node{
     node *next[26]; 
     node(){  //初始化数据 
         memset(next,NULL,sizeof(next));
     }
};

比如我们的字典里头有两个单词,computer和science。
怎么插入单词呢?首先建立一个root根节点。根节点就是初始化的普通节点,相当于一个目录头。然后我们去建立字典树。
两个单词第一个字母分别是c和s,那么我们就建立两个子节点,分别用root的next[‘c’-‘a’]和next[’s’-‘a’]去指向他们。也就是意味着,当某个字母代表的next数组不为空的时候,说明该字母后面有接着这个单词的字母。比如computer代表的路径也就是c->o->m->p->u->t->e->r。
插入单词的代码大概就是这样。

void insert(char *s)//插入新单词,建立字典树
{
     int i,k;
     for(p=root,i=0;s[i];++i)
     {
         k=s[i]-'a';
         if(p->flag==1){
            return true;
         }
         if(p->next[k]==NULL) p->next[k]=new node();//如果next对应的节点为空,就分配一个新节点去储存 
         p=p->next[k]; 
     }
}

怎么去查询呢?我们跟着一个单词往下查询,如果查询到了指向的下一个节点为空或者查询单词到了末尾就退出来,只有当查询单词到了末尾并且字典树到了叶子节点,才说明字典树里面有我们需要的单词。我们只需要在建树的时候用一个flag去标记末尾的节点即可。那么节点就变成了这样。

struct node{
     node *next[26]; 
     int flag;
     node(){  //初始化数据 
         memset(next,NULL,sizeof(next));
         flag=0;
     }
};

查询就是这样

int search(char *s)  //寻找函数 
{
     int i,k;
     for(p=root,i=0;s[i];++i)
     {
         k=s[i]-'a';
         if(p->next[k]==NULL) break;  
         p=p->next[k];
     }
     if(s[i]||p->flag==0) return 0;
     return 1;  
}

这就是基本的字典树了。但感觉字典树比较灵活,还有很多可以做文章的地方。
比如HDU1671 http://acm.hdu.edu.cn/showproblem.php?pid=1671
这个就是判断前面插入的单词会不会是后面单词的前缀。
那么我们只需要在插入的时候,每个单词的末尾都标记一下,后面的单词插入的时候,如果有节点被标记到了,说明前面有单词是这个单词的前缀。就像这样。

bool insert(char * s)
{
     int i,k;
     if(!(p=root))
        p=root=CreateTrieNode();
     for(p=root,i=0;s[i];++i)
     {
         k=s[i]-'0';
         if(p->flag==1){//前面已经都单词出现过 
            return true;
         }
         if(p->next[k]==NULL) p->next[k]=new node();
         p=p->next[k];
     }
     p->flag=1;
     return false;
}

但光想到这样还不行,还需要做一些优化。比如我们可以在分配内存的时候静态分配,这样可以节省很多时间。而不是用new去构造新的node。就像这样

node Memory[60010];
int num=0;
node *CreateTrieNode()
{
    int i;
    node *p;
    p=&Memory[num++];
    p->flag=0;
    for(i=0;i<10;i++)
    {
        p->next[i]=NULL;
    }
    return p;
}

如果是动态分配内存的话,一定要记得在每次输入输出结束后,free掉所有不为空的node,要不然会超内存。各位可以自己去试着实现一下。
至于HDU1251 http://acm.hdu.edu.cn/showproblem.php?pid=1251
就是查询以查询单词为前缀的单词在字典树里出现过多少次。
这也很简单了,在node里用一个cnt保存一下next数组指向的单词数量,那么在查询到单词末尾的时候直接返回cnt即可。就像这样。

struct node{
     int cnt;
     node *next[26]; 
     node(){  
         memset(next,NULL,sizeof(next));
         count=0;
     }
};
node *p,*root=new node();
void insert(char *s)
{
     int i,k;
     for(p=root,i=0;s[i];++i)
     {
         k=s[i]-'a';
         if(p->next[k]==NULL) p->next[k]=new node(); 
         p=p->next[k];
         p->cnt++; 
     }
}
int search(char *s)  
{
     int i,k;
     for(p=root,i=0;s[i];++i)
     {
         k=s[i]-'a';
         if(p->next[k]==NULL) break;
         p=p->next[k];
     }
     if(s[i]) return 0;
     return p->cnt; 
}

以上。

小白一枚,水平有限,如有错误,请多指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值