(轉)淺談trie樹

淺談Trie樹(字典樹)

 

                                                                                            Trie樹(字典樹)php

1、引入html

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

字典樹天然也是起查找做用的。查找的是啥?單詞。ios

看如下幾個題:數組

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

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

好。下一個ide

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

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

judge:n<=200000,TLE!

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

2、原理

在本篇文章中,假設全部單詞都只由小寫字母構成

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

由此能夠看出:

一、字典樹用邊表示字母

二、有相同前綴的單詞公用前綴節點,那咱們能夠的得出每一個節點最多有26個子節點(在單詞只包含小寫字母的狀況下)

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

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

3、基本操做

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

二、代碼

複製代碼
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,查找

查找有不少種,能夠查找某一個前綴,也能夠查找整個單詞。

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

一、思路

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

二、代碼

複製代碼
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;//找到了
}
複製代碼

三、若是是查詢某個單詞的話,咱們用bool變量 v[i]表示節點i是不是單詞結束的標誌。

    那麼最後return的是v[root],因此在插入操做中插入完每一個單詞是,要對單詞最後一個字母的v[i]置爲true,其餘的都是false

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

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

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

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

 

4、完整代碼

一、查詢是否出現

/*
  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;
}

數組模擬
數組模擬

二、查詢前綴出現次數

#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());
    }
}

指針寫法
指針寫法

5、模板題

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

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

做者: xxy
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索