字典樹及其C++實現

蓬萊道人發表於2019-04-08

1、什麼是Trie樹(參考

    Trie樹,即字典樹,又稱單詞查詢樹或鍵樹,是一種樹形結構,是一種雜湊樹的變種。典型應用是用於統計和排序大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:最大限度地減少無謂的字串比較,查詢效率比雜湊表高。Trie的核心思想是空間換時間。利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。

字典樹3個基本性質:

  • 根節點不包含字元,除根節點外每一個節點都只包含一個字元。
  • 從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串。
  • 每個節點的所有子節點包含的字元都不相同。

假設有b,abc,abd,bcd,abcd,efg,hii 這6個單詞,我們構建的樹就是如下圖這樣的:

                             

    如上圖所示,對於每一個節點,從根遍歷到他的過程就是一個單詞,如果這個節點被標記為紅色,就表示這個單詞存在,否則不存在。那麼,對於一個單詞,我只要順著他從根走到對應的節點,再看這個節點是否被標記為紅色就可以知道它是否出現過了。把這個節點標記為紅色,就相當於插入了這個單詞。這樣一來我們查詢和插入可以一起完成,所用時間僅僅為單詞長度,在這一個樣例,便是10。我們可以看到,trie樹每一層的節點數是26^i級別的。所以為了節省空間。我們用動態連結串列,或者用陣列來模擬動態。空間的花費,不會超過單詞數×單詞長度。

字典樹的查詢:

    使用trie時,因為當查詢如字串abc是否為某個字串的字首時,顯然以b,c,d....等不是以a開頭的字串就不用查詢了。所以建立trie的複雜度為O(n*len),而建立+查詢在trie中是可以同時執行的,建立的過程也就可以成為查詢的過程,hash就不能實現這個功能。所以總的複雜度為O(n*len),實際查詢的複雜度也只是O(len)。(說白了,就是Trie樹的平均高度h為len,所以Trie樹的查詢複雜度為O(h)=O(len))。

字典樹優缺點:

優點:

  • 插入,查詢,刪除等操作複雜度為O(h),其中h為單詞的長度。為什麼會這麼快呢,本質是空間換時間(空間複雜度為26的h次方),利用指標來避免做其他不必要的查詢。(初始化的時間複雜度為 n*O(h),n為單詞個數);
  • 當儲存大量單詞或者說儲存的單詞有著共同字首時節省了空間。(比如說用線性儲存boy,boyfriend如用trie儲存的差別);

缺點:

  • 指標佔用的空間,空間複雜度大。如果儲存少量的單詞,並不能節省空間。

字典樹的應用:

  •  字串檢索:事先將已知的一些字串(字典)的有關資訊儲存到trie樹裡,查詢另外一些未知字串是否出現過或者出現頻率。
  • 字串最長公共字首(轉化為尋找共同祖先問題)。

字典樹和雜湊表的比較(參考):

             

2、字典樹的C++實現(參考

#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

const int Num = 26;             //每個節點需要儲存26個字母

struct TrieNode
{
    bool Isword;                //判斷是否是單詞
    TrieNode* next[Num];
    TrieNode() :Isword(false)   //初始化
    {
        memset(next, NULL, sizeof(next));
    }
};

class Trie
{
public:
    Trie() { root = new TrieNode(); }
    void insert(string word);
    bool search(string word);
    void deleteTrie(TrieNode* root);
private:
    TrieNode* root;
};

void Trie::insert(string word)
{
    TrieNode* location = root;
    for (int i = 0; i < word.length();i++)
    {
        if (location->next[word[i] - 'a'] == nullptr)
        {
            TrieNode* temp = new TrieNode();
            location->next[word[i] - 'a']=temp;
        }
        location = location->next[word[i] - 'a'];
    }
    location->Isword = true;
}

bool Trie::search(string word)
{
    TrieNode* location = root;
    //while (word&&location)//注意location不能為空
    for (int i = 0; i < word.length()&&location;i++)
        location = location->next[word[i] - 'a'];
    return(location != NULL && location->Isword);
}

void Trie::deleteTrie(TrieNode* root)
{
    for (int i = 0; i < Num; i++)
    {
        if (root->next[i] != NULL)
        {
            deleteTrie(root->next[i]);
        }
    }
    delete root;
}

void main()                     //簡單測試  
{
    Trie tree;
    int n;                      //輸入n個單詞在字典樹中
    cin >> n;
    while (n--)
    {
        string s;
        cin >> s;
        tree.insert(s);
    }
    string input;
    cout << "輸入要檢查的單詞" << endl;
    cin >> input;
    cout << boolalpha << tree.search(input) << endl;//查詢是否存在是個單詞
}

 

相關文章