1. 程式人生 > >【數據結構】前綴樹/字典樹/Trie

【數據結構】前綴樹/字典樹/Trie

splay 多個 結果 順序 using node 提示 額外 join

【前綴樹】

  用來保存一個映射(通常情況下 key 為字符串 value 為字符串所代表的信息)

  例如:一個單詞集合 words = { apple, cat, water } 其中 key 為單詞 value 代表該單詞是否存在

      words[ ‘apple‘ ] = 存在 而 word[ ‘ abc‘ ] = 不存在

  圖示:一個保存了8個鍵的trie結構,"A", "to", "tea", "ted", "ten", "i", "in", and "inn".

      鍵不需要被顯式地保存在節點中。圖示中標註出完整的單詞,只是為了演示trie的原理。

  技術分享圖片

          

【應用】

  trie樹常用於搜索提示。如當輸入一個網址,可以自動搜索出可能的選擇。當沒有完全匹配的搜索結果,可以返回前綴最相似的可能。

  補充:

Trie是一種非常簡單高效的數據結構,但有大量的應用實例。

(1) 字符串檢索

事先將已知的一些字符串(字典)的有關信息保存到trie樹裏,查找另外一些未知字符串是否出現過或者出現頻率。

舉例:

@ 給出N 個單詞組成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最早出現的順序寫出所有不在熟詞表中的生詞。

@ 給出一個詞典,其中的單詞為不良單詞。單詞均為小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那麽文本problem含有不良單詞。

(2)字符串最長公共前綴

Trie樹利用多個字符串的公共前綴來節省存儲空間,反之,當我們把大量字符串存儲到一棵trie樹上時,我們可以快速得到某些字符串的公共前綴。

舉例:

@ 給出N 個小寫英文字母串,以及Q 個詢問,即詢問某兩個串的最長公共前綴的長度是多少?

解決方案:首先對所有的串建立其對應的字母樹。此時發現,對於兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,於是,問題就轉化為了離線(Offline)的最近公共祖先(Least Common Ancestor,簡稱LCA)問題。

而最近公共祖先問題同樣是一個經典問題,可以用下面幾種方法:

1. 利用並查集(Disjoint Set),可以采用采用經典的Tarjan 算法;

2. 求出字母樹的歐拉序列(Euler Sequence )後,就可以轉為經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題了;

(關於並查集,Tarjan算法,RMQ問題,網上有很多資料。)

(3)排序

Trie樹是一棵多叉樹,只要先序遍歷整棵樹,輸出相應的字符串便是按字典序排序的結果。

舉例:

@ 給你N 個互不相同的僅由一個單詞構成的英文名,讓你將它們按字典序從小到大排序輸出。

(4) 作為其他數據結構和算法的輔助結構

  如後綴樹,AC自動機等

【模板】

  該模板參考白書P209,用數組存儲字典樹

/*
字典樹模板(註意數組初始化問題)
by chsobin 
給定n個單詞的集合,詢問q次:一個單詞是否在集合中 
此處附加信息為節點存不存在 
*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int maxnode = 100;    //maxnode = 單詞長度*單詞數 

int ch[maxnode][27];     
int val[maxnode];        //附加信息 
int sz = 1;                //節點總數 

//插入字符串s,附加信息為v。註意v必須非零,0代表本節點不是單詞節點 
void insert(char * s, int v){
    int u=0;
    int n = strlen(s);
    int c;
    for(int i=0;i<n;++i){
        c = s[i] - a;
        if(!ch[u][c]){ 
            ch[u][c] = sz++;    //新建節點 
        }
        u = ch[u][c];
    } 
    val[u] = v;  //1表示該節點為單詞 
} 

bool find(char * s){
    int u=0;
    int n = strlen(s);
    int c;
    for(int i=0;i<n;++i){
        c = s[i] - a;
        if(ch[u][c]==0) return false;    //單詞不存在
        u = ch[u][c];
    }
    if(val[u]) return true;    //判斷是否為單詞節點 
    else return false;
} 




int main(){
    int n, q;
    char str[30];
    scanf("%d%d", &n, &q);
    while(n--){
        scanf("%s", str);
        insert(str, 1);        //附加信息為1表示該節點為單詞節點 
    } 
    while(q--){
        scanf("%s", str);
        if(find(str)) printf("yes\n");
        else printf("No\n");
    }
    return 0;
}

【例題】

  hdu1251

技術分享圖片
/*
字典樹/前綴樹/Trie
2018年1月28日18:42:23
by chsobin

題目大意:給出n個單詞(單詞長度<=10),
            q個問題,問以給定字符串開頭的單詞有多少 
樸素思想:遍歷 O(qn) 
字典樹:建樹 O(10n) 查詢 O(10q) 
        建樹過程中維護節點額外信息val[i]表示建樹過程中經過節點i的單詞
*/ 
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
using namespace std;

const int maxn = 1000000;
struct Trie{
    int ch[maxn][27];
    int val[maxn];
    int sz;
    Trie(){
        sz = 1;
        memset(val, 0, sizeof(val));
        memset(ch[0], 0, sizeof(ch[0]));
    }

    void insert(char * s){
        int n = strlen(s);
        int u = 0;
        int c;
        for(int i=0;i<n;++i){
            c = s[i] - a;
            if(!ch[u][c]){
                memset(ch[sz], 0, sizeof(ch[sz]));
                ch[u][c] = sz++;
            }
            u = ch[u][c];
            val[u]++;
        }
    }
    
    int find(char * s){
        int n = strlen(s);
        int u = 0;
        int c;
        for(int i=0;i<n;++i){
            c = s[i] - a;
            if(!ch[u][c]){
                return 0; 
            }
            u = ch[u][c];
        } 
        return val[u];
    }
};
Trie trie;
char s[15];
int main(){
    while(gets(s),strcmp(s,"")){
        trie.insert(s);
    }
    while(gets(s)!=NULL){
        printf("%d\n", trie.find(s));
    }
    return 0;
}
ac 78ms

【參考】

  http://dongxicheng.org/structure/trietree/

【數據結構】前綴樹/字典樹/Trie