1. 程式人生 > >Trie樹(轉)

Trie樹(轉)

表示 存在 find 編號 earch ++ rime 代碼 count

原文http://www.cnblogs.com/TheRoadToTheGold/p/6290732.html

一、引入

字典是幹啥的?查找字的。

字典樹自然也是起查找作用的。查找的是啥?單詞。

看以下幾個題:

1、給出n個單詞和m個詢問,每次詢問一個單詞,回答這個單詞是否在單詞表中出現過。

答:簡單!map,短小精悍。

好。下一個

2、給出n個單詞和m個詢問,每次詢問一個前綴,回答詢問是多少個單詞的前綴。

答:map,把每個單詞拆開。

judge:n<=200000,TLE!

這就需要一種高級數據結構——Trie樹(字典樹)

二、原理

在本篇文章中,假設所有單詞都只由小寫字母

構成

對cat,cash,app,apple,aply,ok 建一顆字典樹,建成之後如下圖所示

技術分享圖片

由此可以看出:

1、字典樹用邊表示字母

2、有相同前綴的單詞公用前綴節點,那我們可以的得出每個節點最多有26個子節點(在單詞只包含小寫字母的情況下)

3、整棵樹的根節點是空的。為什麽呢?便於插入和查找,這將會在後面解釋。

4、每個單詞結束的時候用一個特殊字符表示,圖中用的‘′,那麽從根節點到任意一個‘’所經過的邊的所有字母表示一個單詞。

三、基本操作

A、insert,插入一個單詞

1.思路

從圖中可以直觀看出,從左到右掃這個單詞,如果字母在相應根節點下沒有出現過,就插入這個字母;否則沿著字典樹往下走,看單詞的下一個字母。

這就產生一個問題:往哪兒插?計算機不會自己選擇位置插,我們需要給它指定一個位置,那就需要給每個字母編號。

我們設數組trie[i][j]=k,表示編號為i的節點的第j個孩子是編號為k的節點。

什麽意思呢?

這裏有2種編號,一種是i,k表示節點的位置編號,這是相對整棵樹而言的;另一種是j,表示節點i的第j的孩子,這是相對節點i而言的。

不理解?看圖

還是單詞cat,cash,app,apple,aply,ok

我們就按輸入順序對其編第一種號,紅色

表示編號結果。因為先輸入的cat,所以c,a,t分別是1,2,3,然後輸入的是cash,因為c,a是公共前綴,所以從s開始編,s是4,以此類推。

註意這裏相同字母的編號可能不同

技術分享圖片

第二種編號,相對節點的編號,紫色表示編號結果。

因為每個節點最多有26個子節點,我們可以按他們的字典序從0——25編號,也就是他們的ASCLL碼-a的ASCLL碼。

註意這裏相同字母的編號相同

技術分享圖片

實際上每個節點的子節點都應該從0編到——25,但這樣會發現許多事根本用不到的。比如上圖的根節點應該分出26個叉。節約空間,用到哪個分哪個。

這樣編號有什麽用呢?

回到數組trie[i][j]=k。 數組trie[i][j]=k,表示編號為i的節點的第j個孩子是編號為k的節點。

那麽第二種編號即為j,第一種編號即為i,k

2、代碼

技術分享圖片
void insert()//插入單詞s
{
    len=strlen(s);//單詞s的長度
    root=0;//根節點編號為0
    for(int i=0;i<len;i++)
    {
        int id=s[i]-‘a‘;//第二種編號
        if(!trie[root][id])//如果之前沒有從root到id的前綴 
                    trie[root][id]=++tot;//插入,tot即為第一種編號
        root=trie[root][id];//順著字典樹往下走
    }
}
技術分享圖片

B、search,查找

查找有很多種,可以查找某一個前綴,也可以查找整個單詞。

再次我們以查找一個前綴是否出現過為例講解

1、思路

從左往右以此掃描每個字母,順著字典樹往下找,能找到這個字母,往下走,否則結束查找,即沒有這個前綴;前綴掃完了,表示有這個前綴。

2、代碼

技術分享圖片
bool find()
{
    len=strlen(s);
    root=0;//從根結點開始找
    for(int i=0;s[i];i++)
    {
        int x=s[i]-‘a‘;//
        if(trie[root][x]==0)   return false;//以root為頭結點的x字母不存在,返回0 
        root=trie[root][x];//為查詢下個字母做準備,往下走 
    }
    return true;//找到了
}
技術分享圖片

3、如果是查詢某個單詞的話,我們用bool變量 v[i]表示節點i是否是單詞結束的標誌。

那麽最後return的是v[root],所以在插入操作中插入完每個單詞是,要對單詞最後一個字母的v[i]置為true,其他的都是false

4、如果是查詢前綴出現的次數的話,那就在開一個sum[],表示位置i被訪問過的次數,

那麽最後return的是sum[root],插入操作中每訪問一個節點,都要讓他的sum++

這裏前綴的次數是標記在前綴的最後一個字母所在位置的後一個位置上。

比如:前綴abc出現的次數標記在c所在位置的後一個位置上,

技術分享圖片

四、完整代碼

1、查詢是否出現

技術分享圖片
/*
  trie tree的儲存方式:將字母儲存在邊上,邊的節點連接與它相連的字母 
  trie[rt][x]=tot:rt是上個節點編號,x是字母,tot是下個節點編號 
*/ 
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 2000010
using namespace std;
int tot=1,n;
int trie[maxn][26];
//bool isw[maxn];查詢整個單詞用
void insert(char *s,int rt)
{
    for(int i=0;s[i];i++)
    {
        int x=s[i]-a;
        if(trie[rt][x]==0)//現在插入的字母在之前同一節點處未出現過 
        {
            trie[rt][x]=++tot;//字母插入一個新的位置,否則不做處理 
        }
        rt=trie[rt][x];//為下個字母的插入做準備  
    }
    /*isw[rt]=true;標誌該單詞末位字母的尾結點,在查詢整個單詞時用到*/
}
bool find(char *s,int rt)
{
    for(int i=0;s[i];i++)
    {
        int x=s[i]-a;
        if(trie[rt][x]==0)return false;//以rt為頭結點的x字母不存在,返回0 
        rt=trie[rt][x];//為查詢下個字母做準備 
    }
    return true;
    //查詢整個單詞時,應該return isw[rt] 
}
char s[22];
int main()
{
    tot=0;
    int rt=1;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        insert(s,rt);
    }
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        if(find(s,rt))printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

數組模擬
數組

2、查詢前綴出現次數

技術分享圖片
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int trie[400001][26],len,root,tot,sum[400001];
bool p;
int n,m; 
char s[11];
void insert()
{
    len=strlen(s);
    root=0;
    for(int i=0;i<len;i++)
    {
        int id=s[i]-a;
        if(!trie[root][id]) trie[root][id]=++tot;
        sum[trie[root][id]]++;//前綴後移一個位置保存 
        root=trie[root][id];
    }
}
int search()
{
    root=0;
    len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int id=s[i]-a;
        if(!trie[root][id]) return 0;
        root=trie[root][id];
    }//root經過此循環後變成前綴最後一個字母所在位置的後一個位置 
    return sum[root];//因為前綴後移了一個保存,所以此時的sum[root]就是要求的前綴出現的次數 
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        insert();
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        cin>s;
        printf("%d\n",search());
    }
}

數組模擬
數組 技術分享圖片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
char s[11];
int n,m;
bool p;
struct node
{
    int count;
    node * next[26];
}*root;
node * build()
{
    node * k=new(node);
    k->count=0;
    memset(k->next,0,sizeof(k->next));
    return k;
}
void insert()
{
    node * r=root;
    char * word=s;
     while(*word)
    {
        int id=*word-a;
        if(r->next[id]==NULL) r->next[id]=build();
        r=r->next[id];
        r->count++;
        word++;
    }
}
int search()
{
    node * r=root;
    char * word=s;
    while(*word)
    {
        int id=*word-a;
        r=r->next[id];
        if(r==NULL) return 0;
        word++;
    }
    return r->count;
}
int main()
{
    root=build();
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {
            cin>>s;
            insert();
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        cin>>s;
        printf("%d\n",search());
    }
}
指針

五、模板題

hud 1251 統計難題 http://acm.hdu.edu.cn/showproblem.php?pid=1251

codevs 4189 字典 http://codevs.cn/problem/4189/

Trie樹(轉)