字典树(等待更新)

字典树

1.引题

给出一个由S(最多4000个)个不同单词组成的字典和一个长字符串。把这个字符串按字典分解成若干个单词的连接,有多少种方法?(方法数可能很多,结果对20071027取模)
比如有4个单词:a、b、cd、ab,则abcd有两种分解方法:a+b+cd和ab+cd。(字典中单词个数不超过4000)
【输入】
abcd
4
a
b
cd
ab
【输出】
Case 1: 2

你会怎么做呢?去一一比较吗?这也太傻了诶。

所以就有种叫做字典树的东西啦!

字典树(trie),又称前缀树。是查找树的一种。主要用于大量字符串的存储,查找。搜索引擎通常利用字典树完成用户的搜索提示。


比如像这样的图,tea,ted,ten,to,inn就被我分解成了一个这样的树,也就是说,字典树在本质上是一棵26叉树。当你要找一个单词是,就是从第一个字母开始往下找,直到找到叶子,就找到了这个单词。

那么具体怎么实现呢?

先以引题为例子,
1.用d[i]表示以第i个字符到字符串末尾的字符串能够分解得单词数,
2.假设在s[i…length(s)]中,仅发现s[i…n]是字典中出现的单词,n是该单词的长度,此时d[i]=d[i+n],(这里该怎么理解呢?这样说,因为该单词是仅发现的,说明从i到n里有也只有这么一个单词不能再拆分了,所以d[i]=1*d[i+n])
3.所以,d[i]=sum(d[i+len(x)]),假定x是一个出现的前缀字符串(2中的s[i…n])
4.但是,如果枚举x,再判断它是否为s[i…len]的前缀,复杂度是O(4000*len);

那么,字典树!

以该图为例
我们很容易发现字典树有以下几个特点:
1.根节点为空,
2.内节点一般为字符
3.叶子结点就是往下搜到的单词结点
然后呢,用两个数组来保存这棵树
1.ch[i][j]=x,表示第i号结点的子结点序号是x,保存的字符的ASCII码是j;
2.val[i],表示第i号结点的附加信息,比如val[i]>0表示i是单词结点。(这个val数组是要你自己在造树的时候遇到叶子结点再把它手动赋值为大于0)

接下来展示字典树的插入和查询

const int maxnode = 400*100+10;
const int sigma=26;
struct Tire {
  int ch[maxnode][sigma];
  int val[maxnode];
  int sz; //结点总数
  void clear() {
    sz=1; memset(ch[0], 0, sizeof(ch[0]));
  }
  int idx(char c) {return c-'a'};
  void insert(char *s, int v);
  void find(char *s);
}
void insert(char *s, int v) //v是附加信息,0表示“非单词结点”
  {
    int u=0, n = strlen(s);
    for (int i=0; i<n; i++) {
      int c = idx(s[i]);//得出它对于a的相对值
      if (!ch[u][c]) //如果是0,那么申请新节点
      {
        memeset(ch[sz], 0, sizeof(ch[sz]));
        val[sz]=0;//说明他不是叶子结点(这里我还不清楚,麻烦各位dalao评论指正啦!!)
        ch[u][c]=sz++;//申请新的结点
      }
      u = ch[u][c];//接下来的字符则是以u为根节点往下延伸了,就是比如说beautiful,当e作为b的子节点之后,就要去做a是e的子节点了
    }
    val[u]=v;
  } 
  void find(const char *s, int len, vector<int>& ans)
  {
    int u=0;
    for (int i=0; i<len; i++) {
      if (s[i] == '\0') break;//空节点不做
      int c=idx(s[i]);
      if (!ch[u][c]) break;//这一层这个序号的这个字符的子节点没有在字典里,我就不做啦哈哈哈哈哈
      u = ch[u][c];//往子节点延伸
      if (val[u]>0) ans.push_back(val[u]); //到叶子结点了,那么就是个单词
    }
  }
} 

char str[30001], tmp[101];
int d[30001];
Trie t;
int main()
{
    while(scanf("%s",str) != EOF){
        scanf("%d",&n);
        t.init();
        for(int i = 0 ; i < n ; i ++){
            scanf("%s",tmp);
            int len = strlen(tmp);
               //将单词的长度保存的trie树节点中
            t.insert(tmp,len);
        }
        printf("Case %d: ",Case++);
        solve();
    }
    return 0;
}
void solve()
{
    memset(d,0,sizeof(d));
    int len = strlen(str);
    d[len] = 1;         
    for(int i = len - 1; i >= 0 ; i--){
        t.find(str+i,len-i);
        for(int j = 0 ; j < ans.size(); j++){
            d[i] = (d[i]+d[i+ans[j]])%MOD;//一开始的那道题的解法,我明天来讲!!!!
        }
    }
    printf("%d\n",d[0]);
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值