Trie樹(字典樹)相關概念以及實現和練習

Trie樹(字典樹)相關概念以及實現和練習

Trie樹基本概述

Trie樹,又稱字典樹或前綴樹,是一種有序的、用於統計、排序和存儲字符串的數據結構,它的關鍵字不是保存在節點中,而是由節點在樹中的位置決定
在這裏插入圖片描述
一個節點的所有子孫都具有相同的前綴,也就是這個節點對應的字符串,根節點對應空字符串。一般情況下,不是所有的節點都有對應的值,只有葉子節點和部分內部節點所對應的鍵才具有相關的值。

trie樹最大優點是利用字符串的公共前綴來減少存儲空間和查詢時間,從而最大限度地減少無謂的字符串比較,非常高效。

trie的應用:單詞自動補全、拼寫檢查、最長前綴匹配等

Trie的結點結構:

struct TrieNode
{
     bool is_end; //表示是否是一個字符串的結尾
     TrieNode *child[26]; //a-z 26個字母
     TrieNode():is_end(false){
         for(int i=0;i<26;i++)
             child[i]='a'+i;
     }  	
};

208. 實現 Trie (前綴樹)

實現一個 Trie (前綴樹),包含 insert, search, 和 startsWith 這三個操作。

示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true

分析:

Trie樹的單詞插入:

  • 構建ptr指針指向root
  • 逐個遍歷字符串中的各個字符
    • 計算下標pos=正在遍歷的字符-‘a’
    • 如果ptr指向的結點的第pos個孩子爲假:
      • 創建該結點的第pos個孩子
    • ptr指向該結點的第pos個孩子
  • 標記ptr指向的結點的is_end爲true

Trie樹的搜索:

  • 使用ptr指針指向root
  • 逐個遍歷字符串中的各個字符
    • 計算下標pos=正在遍歷的字符-‘a’
    • 如果ptr指向的結點不存在,則返回false
    • 否則ptr指向該結點的第pos個孩子
  • 返回ptr指向的結點的is_end

Trie樹搜索是否存在給定前綴:

  • 與搜索思路一致,遍歷每個字符,直至遍歷結束。期間,如果ptr指向的結點不存在則返回false。如果遍歷該前綴結束,則說明至少該前綴屬於一個單詞,因爲如果不屬於任何單詞的話是沒辦法遍歷至最後一個字母的。
struct TrieNode
{
     bool is_end; //表示是否是一個字符串的結尾
     TrieNode *child[26];   //位置即字符,不需要另外存儲
     TrieNode():is_end(false){
         for(int i=0;i<26;i++)
             child[i]=0;
     }  	
};
class Trie {
public:
    /** Initialize your data structure here. */
    Trie() {

    }
    ~Trie()
    {
        for(int i=0;i<node_vec.size();i++)
        {
            delete node_vec[i];
        }
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) 
    {
        TrieNode* ptr=&root;
        for(auto s:word)
        {
            int pos=s-'a'; //位置
            if(!ptr->child[pos]) //所指的孩子結點不存在
            {
                ptr->child[pos]=new_node(); //創建新結點
            }
            ptr=ptr->child[pos];
        }
        ptr->is_end=true; //表示以該位置結尾的路徑有單詞
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) 
    {
        TrieNode* ptr=&root;
        for(auto s:word)  //遍歷,如果遍歷時發現該路徑不存在,也就是該字符不存在,則返回false
        {
            int pos=s-'a';
            if(!ptr->child[pos])
                return false;
            ptr=ptr->child[pos];
        }
        return ptr->is_end==true;  //遍歷至結尾了只需要判斷該位置是否是一個string的結尾即可
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) 
    {
        TrieNode* ptr=&root;
        for(auto s:prefix)  //遍歷,如果遍歷時發現該路徑不存在,也就是該字符不存在,則返回false
        {
            int pos=s-'a';
            if(!ptr->child[pos])
                return false;
            ptr=ptr->child[pos];
        }
        return true;
    }
    private:
        TrieNode* new_node()
        {
            TrieNode *node=new TrieNode();
            node_vec.push_back(node);
            return node;
        }
        vector<TrieNode*> node_vec;
        TrieNode root;
};

648. 單詞替換

在英語中,我們有一個叫做 詞根(root)的概念,它可以跟着其他一些詞組成另一個較長的單詞——我們稱這個詞爲 繼承詞(successor)。例如,詞根an,跟隨着單詞 other(其他),可以形成新的單詞 another(另一個)。

現在,給定一個由許多詞根組成的詞典和一個句子。你需要將句子中的所有繼承詞用詞根替換掉。如果繼承詞有許多可以形成它的詞根,則用最短的詞根替換它。

你需要輸出替換之後的句子。

示例:
輸入:dict(詞典) = ["cat", "bat", "rat"] sentence(句子) = "the cattle was rattled by the battery"
輸出:"the cat was rat by the bat"

分析:使用字典樹

  • 建立字典樹(字典樹至少包括兩個功能:插入單詞和查找某一單詞的詞根)
  • 將所有詞典中的單詞插入字典樹中
  • 分割句子中的單詞,然後用詞根替換
  • 返回最終的句子

代碼:

struct TrieNode
{
     bool is_end; //表示是否是一個字符串的結尾
     TrieNode *child[26];   //位置即字符,不需要另外存儲
     TrieNode():is_end(false){
         for(int i=0;i<26;i++)
             child[i]=0;
     }  	
};
class Trie {
public:
    /** Initialize your data structure here. */
    Trie() {

    }
    ~Trie()
    {
        for(int i=0;i<node_vec.size();i++)
        {
            delete node_vec[i];
        }
    }
    
    /** Inserts a word into the trie. */
    void insert(string word)  //向字典樹中插入單詞
    {
        TrieNode* ptr=&root;
        for(auto s:word)
        {
            int pos=s-'a'; //位置
            if(!ptr->child[pos]) //所指的孩子結點不存在
            {
                ptr->child[pos]=new_node(); //創建新結點
            }
            ptr=ptr->child[pos];
        }
        ptr->is_end=true; //表示以該位置結尾的路徑有單詞
    }
    
    /** Returns if the word is in the trie. */
    string search(string word) 
    {
        TrieNode* ptr=&root;
        string result="";
        for(auto s:word)  //遍歷,如果某個前綴的is_end是true則返回該前綴
        {
            int pos=s-'a';
            if(!ptr->child[pos])
                return word;
            ptr=ptr->child[pos];
            result+=s;
            if(ptr->is_end==true)
                return result;
        }
        return result;  //遍歷至結尾了只需要判斷該位置是否是一個string的結尾即可
    }
    private:
        TrieNode* new_node()
        {
            TrieNode *node=new TrieNode();
            node_vec.push_back(node);
            return node;
        }
        vector<TrieNode*> node_vec;
        TrieNode root;
};
class Solution {
public:
    string replaceWords(vector<string>& dict, string sentence) 
    {
        Trie *root=new Trie();
        for(int i=0;i<dict.size();i++)
        root->insert(dict[i]); //構建字典樹
        string word="";
        string result="";
        for(int i=0;i<sentence.size();i++)  
        {
            if(sentence[i]!=' ') //如果不是空格則繼續加單詞
            {
                word+=sentence[i];
            }
            if(sentence[i]==' ')  //如果遇到空格說明完整單詞,將該單詞替換即可
            {
                result+=root->search(word);
                result+=' ';
                word="";
            }
        }
        result+=root->search(word);  //最後一個單詞替換
        return result;
        
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章