Trie树(HDU 1251+1075)

Trie树有一些特性:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
5)插入查找的复杂度为O(n),n为字符串长度。

怎么说呢。Trie树其实是把单词(以单词为例)的每个字母依次连成一条链,每个单词都连在一个根节点上,如果两个单词拥有相同的一部分元素,那这两条链之间可能有部分重合。
这里写图片描述
图片是网上找的。。
以上图为例,红色节点标记单词结束。最左一条链有两个单词ab和abc,他们有部分重合。
那其实写程序的时候也是按照相同的方式建树。
我习惯是用数组。

int index[N][30];//index存储的是树上的点u经过字母t所指向的点。
int num[N];
//记录该字母在这条链上出现的次数,需要根据具体题目调整想要记录的数据
int cnt=0;

void Insert(char str[]){
    int u=0;
    for(int i=0;i<strlen(str);i++){
        int tmp=str[i]-'a'+1;
        if(index[u][tmp]==0)
            index[u][tmp]=++cnt;
        num[u]++;
        u=index[u][tmp];
    }
    num[u]++;
}

查询的时候也是一样的方式
从根节点开始查起,一层一层往下走,找到你想要找的单词的末尾,然后取得相应的数据。

int Query(char str[]){
    int u=0;
    for(int i=0;i<strlen(str);i++){
        int tmp=str[i]-'a'+1;
        if(!index[u][tmp])
            return 0;
        u=index[u][tmp];
    }
    return num[u];
}

然后看下具体题目
http://acm.hdu.edu.cn/showproblem.php?pid=1251
这是一道根据给定前缀求单词数量的问题。就是很裸的trie树。
建树:将单词表中每个单词插进去,每次记录一个每个节点的数目(表明有多少个单词经过该点)
然后直接根据前缀查询。

#include <bits/stdc++.h>
#define N 1000010
using namespace std;
int index[N][30];
int num[N];
int cnt=0;

void Insert(char str[]){
    int u=0;
    for(int i=0;i<strlen(str);i++){
        int tmp=str[i]-'a'+1;
        if(index[u][tmp]==0)
            index[u][tmp]=++cnt;
        num[u]++;
        u=index[u][tmp];
    }
    num[u]++;
}
int Query(char str[]){
    int u=0;
    for(int i=0;i<strlen(str);i++){
        int tmp=str[i]-'a'+1;
        if(!index[u][tmp])
            return 0;
        u=index[u][tmp];
    }
    return num[u];
}
int main(){
    char str[15];
    while(gets(str)){
        if(strlen(str)==0)
            break;
        Insert(str);
    }
    while(cin>>str){
        int cnt=Query(str);
        cout<<cnt<<endl;
    }
    return 0;
}

上述题目其实最暴力的想法是把每个前缀跟每个单词进行比对,这样效率是O(m*n*len(m)),其中n是单词个数,m是前缀个数,len(m)是前缀长度。
然而使用trie树建树是O(n*len(n)),查询是 O(m*len(m))。相比较,效率会高很多。

http://acm.hdu.edu.cn/showproblem.php?pid=1075
这道题是一道替换单词的题,可以用map写,但据说trie树的效率更高。没写map,因为想找trie练手。
刚开始看到的时候,虽然知道知识点是trie,但是相较于前一题,这该怎么存替换的单词啊。然后发现我们可以在每个单词的末尾存储对应的替换单词,其余节点则不用管。查询的时候直接返回所查询的单词末尾存储的数据。
我的代码是PE,还没找到哪里有问题。。

#include <bits/stdc++.h>
#define N 1000010
using namespace std;
int index[N][30];
string word[N];
int cnt=0;

void Insert(string str1,string str2){
    int u=0;
    for(int i=0;i<str2.size();i++){
        int tmp=str2[i]-'a';
        if(!index[u][tmp])
            index[u][tmp]=++cnt;
        u=index[u][tmp];
    }
    word[u]=str1;
}
string Query(string str){
    int u=0;
    string s="";
    for(int i=0;i<str.size();i++){
        int tmp=str[i]-'a';
        if(!index[u][tmp])
            return s;
        u=index[u][tmp];
    }
    return word[u];
}
int main(){
    string str1,str2;
    cin>>str1;
    while(cin>>str1>>str2){
        if(str1=="END")
            break;
        Insert(str1,str2);
    }
    while(getline(cin,str1)){
        if(str1=="END")
            break;
        int l=0,r=0,i=0,pre=0;
        while(i<str1.size()){
            while(i<str1.size()&&(!isalpha(str1[i])))
                i++;
            l=i;
            for(int j=pre;j<l;j++)
                cout<<str1[j];
            if(i>=str1.size())
                break;
            while(i<str1.size()&&isalpha(str1[i]))
                i++;
            r=i;
            if(i>=str1.size())
                break;
            string s=str1.substr(l,r-l);
            string tp=Query(s);
            if(tp.size())
                cout<<tp;
            else{
                for(int j=l;j<r;j++)
                    cout<<str1[j];
            }
            if(i>=str1.size())
                break;
            pre=r;
        }
        cout<<endl;
    }
    return 0;
}

其实感觉基础的trie树还是很好理解的。就根据脑子里面的图建一棵树出来,然后查询的时候从上向下走相应的链就行了。唯一不好想的可能就是数据存储,以及怎么想到某道题是trie树。

猜你喜欢

转载自blog.csdn.net/vermouth_x/article/details/80369776