淺談Trie樹


Trie樹,也叫字典樹。顧名思義,它就是一個字典

字典是干什么的?查找單詞!(英文字典哦)

個人認為字典樹這個名字起得特別好,因為它真的跟字典特別像,一會r你就知道了。

注:trie的中文翻譯就是單詞查找樹

 

一、引入

先來看一個題:

給你n個單詞構成一個字典,再給你一個單詞,問此單詞在字典中有沒有出現。

簡單,暴力!

時間復雜度:n*單詞長度

 

再來看一個題:

給你n個單詞構成一個字典,再給你m個單詞,問這m個單詞在字典中有沒有出現。

再暴力!

時間復雜度:n*單詞長度+m*n*單詞長度

n≤1e4,m≤1e4,單詞長度≤1e3

boom!

(╯‵□′)╯︵┻━┻

所以,為什么要學習使用Trie樹?

因為它快!

 

二、概念

我們首先來看看Trie樹是啥

↑就是它

我們來解剖一下,好好研究研究(Trie樹:瑟瑟發抖)

首先,我們發現樹的每條邊上都有一個字母

這就是Trie樹的樣子,每條邊上都有一個字母,每個頂點代表從根到該節點的路徑所對應的字符串(根結點除外)

其次,有些節點是紅色的,有些則不是

這是什么意思呢?

結點不是代表單詞嘛

所以如果結點標紅,代表該單詞在字典中實際出現過

如果看不太懂,不要緊,繼續往下

 

三、插入單詞(構建Trie樹)

這里進入正題。第一步,構建trie樹

就好比你想要查找單詞,首先得有字典才行吧

step1:初始化

Trie樹為空,只包含一個孤零零的根結點

step2:插入單詞(注:這里假設所有單詞僅由小寫字母構成)

插入一個單詞的步驟如下:

(1)對於Trie樹,我們從根結點開始,設該節點為P;對於這個單詞,我們從第一個字母開始,設此字符為s

(2)掃描P下方的所有邊,看s有沒有出現過

如果出現了,設s與P→Q這條邊上的字符相同,則P=Q

如果沒有出現,另建一條邊,使該邊上的字母為s,新節點為Q,然后P=Q

(3)s變為該單詞的下一個字符,重復步驟2,直到掃描完整個單詞為止


 

以概念中的那個圖為例

首先我們要插入abc這個單詞

(1)P為根結點,s為'a'

發現根結點下方無'a',所以新建一條邊

(2)P為a下方那個結點,s為'b'

發現P下方無'b',所以新建一條邊

(3)P為b下方那個結點,s為'c'

發現P下方無'c',所以新建一條邊

(4)發現abc這個單詞插入完成,所以在當前的s做個標記,表示abc是一個出現過的單詞

接着我們按照相同的步驟插入bcd、efg和hij

接下來插入abcd

(1)P為根結點,s為'a'

發現根結點下方有'a',所以P變為'a'下方的結點

(2)P為a下方那個結點,s為'b'

發現P下方有'b',所以P變為'b'下方的結點

(3)P為b下方那個結點,s為'c'

發現P下方有'c',所以P變為'c'下方的結點

(4)P為c下方那個紅色的結點,s為'd'

發現P下方無'd',所以所以新建一條邊

(5)單詞abcd插入完畢,將當前的s做標記

 下面按照相同的方法,繼續插入單詞abd和b

插入完成后Trie樹如下:

看看一樣不:

(右下那條邊應該是j)

我們可以發現,在Trie樹中有相同前綴的單詞共用相同的前綴,這樣就可以大大的優化空間和時間

插入單詞的時間復雜度為O(NE)(N為單詞個數,E為單詞長度)

參考代碼:

void insert(char *s)//s為要插入的字符串 
{
    int len=strlen(s);
    int u=1;//1為根節點 
    for(int i=0;i<len;i++)
    {
        int c=s[i]-'a';//'a'有時需換成'A'或'0' 
        if(!trie[u][c])//沒有共同前綴,建立一個新的 
            ch[u][c]=++tot;//tot為總點數 
        u=ch[u][c];//繼續向下插入單詞 
    }
    book[u]=true;//標記是一個出現過的單詞(圖中塗紅色) 
}
insert

 

四、查找單詞

詞典有了,接下來就可以查詞了

在trie樹中查單詞就跟查字典一樣。先查首字母,然后第二個,第三個……

查找過程跟插入的過程很像:

 

(1)對於Trie樹,我們從根結點開始,設該節點為P;對於這個單詞,我們從第一個字母開始,設此字符為s

(2)掃描P下方的所有邊,看s有沒有出現過

如果出現了,設s與P→Q這條邊上的字符相同,則P=Q

如果沒有出現,則該單詞沒有出現過,直接返回false

(3)s變為該單詞的下一個字符,重復步驟2,直到掃描完整個單詞為止

(4)掃描完成后,判斷節點P有沒有被標記(是不是某個出現過的單詞的結尾)。如果標記了,那么該單詞出現過,返回true;如果沒有標記,那么該單詞是詞典中這個單詞的前綴,返回false。

對於(4)講解一下:

還是這棵Trie樹:

我們查找ef這個單詞:

(1)P為根結點,s為'e'

發現根結點下方有'e',所以P變為'e'下方的結點

(2)P為e下方那個結點,s為'f'

發現P下方有'f',所以P變為'f'下方的結點

(3)查找完成,發現P這里沒有標記,所以ef是詞典中單詞efg的前綴,而不是直接出現在了詞典里,返回false

再舉一個例子,我們查找abd這個單詞:

(1)P為根結點,s為'a'

發現根結點下方有'a',所以P變為'a'下方的結點

(2)P為a下方那個結點,s為'b'

發現P下方有'b',所以P變為'b'下方的結點

(3)P為b下方那個結點,s為'd'

發現P下方有'd',所以P變為'd'下方的結點

(3)查找完成,發現P這里有標記,所以abd是詞典中的單詞,返回true

 

應該講的挺明白的

查找的一個單詞的時間復雜度O(E),比起暴力的O(NE)要快多了

參考代碼:

bool find(char *s)//s為要查找的字符串 
{
    int len=strlen(s);
    int u=1;//1為根節點 
    for(int i=0;i<len;i++)
    {
        int c=s[i]-'a';//'a'有時需換成'A'或'0' 
        if(!trie[u][c])//單詞沒有出現,直接返回false 
            return false;
        u=ch[u][c];//繼續向下查找單詞 
    }
    //如果掃描完了這個單詞 
    return true;//是某個單詞的前綴 
}
查找單詞是否是詞典中某單詞的前綴
bool find(char *s)//s為要查找的字符串 
{
    int len=strlen(s);
    int u=1;//1為根節點 
    for(int i=0;i<len;i++)
    {
        int c=s[i]-'a';//'a'有時需換成'A'或'0' 
        if(!trie[u][c])//單詞沒有出現,直接返回false 
            return false;
        u=ch[u][c];//繼續向下查找單詞 
    }
    //如果掃描完了這個單詞 
    return book[u];//如果出現過,返回true;如果沒有出現過(是前綴),返回false 
}
查找單詞是否在詞典中出現過

 

模板題:

https://www.cnblogs.com/llllllpppppp/p/9366344.html

 

本文部分圖片來源於網絡

部分內容參考《信息學奧賽一本通.提高篇》第二部分第三章 Trie字典樹

若需轉載,請注明https://www.cnblogs.com/llllllpppppp/p/9449846.html

 

~祝大家編程順利~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM