zoukankan      html  css  js  c++  java
  • [LeetCode]Implement Trie (Prefix Tree)

    题目:Implement Trie (Prefix Tree)

    实现字典树。

    什么是字典树?

    Trie树,又叫字典树、前缀树(Prefix Tree)、单词查找树 或 键树,是一种多叉树结构。如下图:

    上图是一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。从上图可以归纳出Trie树的基本性质:

    1. 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
    2. 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
    3. 每个节点的所有子节点包含的字符互不相同。

    通常在实现的时候,会在节点结构中设置一个标志,用来标记该结点处是否构成一个单词(关键字)。

    可以看出,Trie树的关键字一般都是字符串,而且Trie树把每个关键字保存在一条路径上,而不是一个结点中。另外,两个有公共前缀的关键字,在Trie树中前缀部分的路径相同,所以Trie树又叫做前缀树(Prefix Tree)。

    更详细的内容请参照:http://www.cnblogs.com/yeqluofwupheng/p/6793516.html

    树的节点结构如下:

    struct TrieNode{
            int count = 0;//该节点代表的单词的个数,由此判断当前节点是否构成一个单词
            vector<TrieNode*> children;//26个小写字母,以它们的值为下标
        };

    上面count用来记录访问次数,同时也可以标志从根节点到当前节点的字符串是否为一个单词。

    第一次插入的时候讲最后一个字母对应的树节点的count设为1.

    下面包括void insert(string word);bool search(string word);bool startsWith(string prefix);三个操作。

    class Trie {
    public:
        /** Initialize your data structure here. */
        Trie() {
            root = new TrieNode();
            root->count = 0;
            for (size_t i = 0; i < 26; i++)    {
                root->children.push_back(nullptr);
            }
        }
    
        /** Inserts a word into the trie. */
        void insert(string word) {
            auto it = word.cbegin();
            auto p = root;
            while (it != word.cend()){
                auto pos = *it - 'a';
                if (!p->children.at(pos)){//创建字典树节点
                    TrieNode* newChild = new TrieNode();
                    newChild->count = 0;
                    for (size_t i = 0; i < 26; i++)    {
                        newChild->children.push_back(nullptr);
                    }
                    p->children.at(pos) = newChild;//连接
                }
                p = p->children.at(pos);
                ++it;
            }
            ++(p->count);//增加他的访问,p是最后的节点
        }
    
        /** Returns if the word is in the trie. */
        bool search(string word) {
            auto it = word.cbegin();
            auto p = root;
            while (it != word.cend()){//正常退出表示找到了该单词的末尾位置
                auto pos = *it - 'a';
                if (!p->children.at(pos))return false;//不能构成单词
                p = p->children.at(pos);
                ++it;
            }
            if (p->count)return true;//count>0表示当前节点构成一个单词
            return false;
        }
    
        /** Returns if there is any word in the trie that starts with the given prefix. */
        bool startsWith(string prefix) {
            auto it = prefix.cbegin();
            auto p = root;
            while (it != prefix.cend()){//正常退出表示找到了该单词的末尾位置
                auto pos = *it - 'a';
                if (!p->children.at(pos))return false;//不能构成单词
                p = p->children.at(pos);
                ++it;
            }
            return true;
        }
    private:
        struct TrieNode{
            int count = 0;//该节点代表的单词的个数,由此判断当前节点是否构成一个单词
            vector<TrieNode*> children;//26个小写字母,以它们的值为下标
        };
        TrieNode* root;
    };

    题目:Add and Search Word - Data structure design

    字典树的简单应用,在查找上增加了'.'的正则表达式;注意题目中只有一个'.',而没有省略号,不要搞错了。

    基本思想就是使用字典树,树节点结构,创建,添加和上面都是一样的。主要在查找上有一点区别。

    class WordDictionary {
    public:
        /** Initialize your data structure here. */
        WordDictionary() {
            dict = new TrieNode();
            dict->isKey = false;
            for (size_t i = 0; i < 26; i++)    {
                dict->children.push_back(nullptr);
            }
        }
    
        /** Adds a word into the data structure. */
        void addWord(string word) {
            auto it = word.cbegin();
            auto p = dict;
            while (it != word.cend()){
                auto pos = *it - 'a';
                if (!p->children.at(pos)){
                    TrieNode* newChild = new TrieNode();
                    newChild->isKey = false;
                    for (size_t i = 0; i < 26; i++)    {
                        newChild->children.push_back(nullptr);
                    }
                    p->children.at(pos) = newChild;
                }
                p = p->children.at(pos);
                ++it;
            }
            p->isKey = true;
        }
    
        /** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
        bool search(string word) {
            return query(word, dict);
        }
    private:
        struct TrieNode{
            bool isKey = false;//标记从树根到断当前节点是否构成一个单词
            vector<TrieNode*> children;//26个小写字母,以它们的值为下标
        };
        TrieNode* dict;
    
        bool query(string word, TrieNode* root){
            if (!root)return false;
            auto p = root;
            for (size_t i = 0; i < word.size(); ++i){//正常退出表示找到了该单词的末尾位置
                if (word[i] == '.'){//一个点
                    string sub = i == word.size() - 1 ? word.substr(i, 0) : word.substr(i + 1, word.size() - 1 - i);
                    for (size_t i = 0; i < p->children.size(); i++){
                        if (query(sub, p->children.at(i)))return true;//找到符合的单词
                    }
                    return false;
                }
                auto pos = word[i] - 'a';
                if (!p->children.at(pos))return false;//不能构成单词
                p = p->children.at(pos);
            }
            if (p->isKey)return true;//count>0表示当前节点构成一个单词
            return false;
        }
    };

    bool search(string word)需要使用递归实现,所以有了query()的实现。主要就是当遇到'.'的时候循环递归判断当前树节点的每一个子节点。

    注意:

    1.空串的情况:string sub = i == word.size() - 1 ? word.substr(i, 0) : word.substr(i + 1, word.size() - 1 - i);此时单词的最后一个字符是'.',substr中不能传i+1;

    2.空节点的情况:if (!root)return false;

    题目:Word Search II

    给定一个字符数组和一组单词,将单词的每个字母在字符数组中邻接(单词前一个字母和后一个字母是在前后左右某一个方向上相邻)的单词输出。

    例如

    Given words = ["oath","pea","eat","rain"] and board =

    [
      ['o','a','a','n'],
      ['e','t','a','e'],
      ['i','h','k','r'],
      ['i','f','l','v']
    ]
    

    Return ["eat","oath"].

    思路:

    使用Trie树,将单词放入空的Trie树中,然后遍历字符数组,递归查找,每当找到Trie树的单词节点(可以组成一个单词的节点),就将单词放入数组中,最后返回该数组即可。

    实现如下:

    Trie树的构造方式:

    class TrieNode{
    public:
        int count;//该节点代表的单词的个数,由此判断当前节点是否构成一个单词
        vector<TrieNode*> children;//26个小写字母,以它们的值为下标
        TrieNode() :count(0){
            children = vector<TrieNode*>(26, nullptr);
        }
    };
    
    class Trie {
    public:
        /** Initialize your data structure here. */
        Trie() {
            root = new TrieNode();
        }
        Trie(vector<string>& words) {
            root = new TrieNode();
            for (size_t i = 0; i < words.size(); ++i)    {
                insert(words.at(i));
            }
        }
    
        TrieNode* getRoot(){ return root; }
    
        /** Inserts a word into the trie. */
        void insert(string word) {
            auto it = word.cbegin();
            auto p = root;
            while (it != word.cend()){
                auto pos = *it - 'a';
                if (!p->children.at(pos)){//创建字典树节点
                    TrieNode* newChild = new TrieNode();
                    newChild->count = 0;
                    for (size_t i = 0; i < 26; i++)    {
                        newChild->children.push_back(nullptr);
                    }
                    p->children.at(pos) = newChild;//连接
                }
                p = p->children.at(pos);
                ++it;
            }
            p->count = 1;//增加他的访问,p是最后的节点
        }
    
        /** Returns if the word is in the trie. */
        bool search(string word) {
            auto it = word.cbegin();
            auto p = root;
            while (it != word.cend()){//正常退出表示找到了该单词的末尾位置
                auto pos = *it - 'a';
                if (!p->children.at(pos))return false;//不能构成单词
                p = p->children.at(pos);
                ++it;
            }
            if (p->count)return true;//count>0表示当前节点构成一个单词
            return false;
        }
    
        /** Returns if there is any word in the trie that starts with the given prefix. */
        bool startsWith(string prefix) {
            auto it = prefix.cbegin();
            auto p = root;
            while (it != prefix.cend()){//正常退出表示找到了该单词的末尾位置
                auto pos = *it - 'a';
                if (!p->children.at(pos))return false;//不能构成单词
                p = p->children.at(pos);
                ++it;
            }
            return true;
        }
    private:
        friend class TrieNode;//不能丢,否则会报“不能将TrieNode*类型的值分配到TrieNode*类型的实体”的错误
        TrieNode* root;
    };

    这里完全使用C++的语法实现,注意Trie中friend class TrieNode;不能少;

    其他和上面类似,不再赘述。

    vector<string> LeetCode::findWords(vector<vector<char>>& board, vector<string>& words){
        Trie tree(words);
        set<string> result;//防止添加重复的单词
        for (size_t i = 0; i < board.size(); ++i){
            for (size_t j = 0; j < board.at(0).size(); ++j){
                findWords(board, i, j, tree.getRoot(), string(""), result);
            }
        }
        vector<string> strs;
        for each (auto s in result){
            strs.push_back(s);
        }
        return strs;
    }
    
    void LeetCode::findWords(vector<vector<char>>& board, int i, int j, TrieNode* root, string& word, set<string>& result){
        if (i < 0 || i >= board.size() || j < 0 || j >= board.at(0).size() || board.at(i).at(j) == ' ')return;//越界返回
        TrieNode* p = root->children.at(board.at(i).at(j) - 'a');
        if (p){
            word += board.at(i).at(j);
            if (p->count)result.insert(word);//找到一个单词
            char ch = board.at(i).at(j);
            board.at(i).at(j) = ' ';//一个字符不能重复使用
            findWords(board, i - 1, j, p, word, result);//左边
            findWords(board, i, j - 1, p, word, result);//下边
            findWords(board, i + 1, j, p, word, result);//右边
            findWords(board, i, j + 1, p, word, result);//上边
            board.at(i).at(j) = ch;
            word.pop_back();
        }
    }

    注意:

    1.单词的回溯

    word.pop_back();

    2.避免字符数组中字符的重复使用

    board.at(i).at(j) = ' ';//一个字符不能重复使用

    上面为了避免重复的单词,使用了set容器,实际上因为有Trie树中count字段代表频度,可以使用它来避免重复单词。

    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
            Trie tree(words);
            vector<string> result;//防止添加重复的单词
            string word;
            for (size_t i = 0; i < board.size(); ++i){
                for (size_t j = 0; j < board.at(0).size(); ++j){
                    findWords(board, i, j, tree.getRoot(), word, result);
                }
            }
            return result;
        }
        
        void findWords(vector<vector<char>>& board, int i, int j, TrieNode* root, string& word, vector<string>& result){
            if (i < 0 || i >= board.size() || j < 0 || j >= board.at(0).size() || board.at(i).at(j) == ' ')return;//越界返回
            TrieNode* p = root->children.at(board.at(i).at(j) - 'a');
            if (p){
                word += board.at(i).at(j);
                if (p->count == 1){//初次(除了创建树的时候)访问时,添加
                    result.push_back(word);//找到一个单词
                    p->count += 1;//访问过一次频度加一
                }
                char ch = board.at(i).at(j);
                board.at(i).at(j) = ' ';//一个字符不能重复使用
                findWords(board, i - 1, j, p, word, result);//左边
                findWords(board, i, j - 1, p, word, result);//下边
                findWords(board, i + 1, j, p, word, result);//右边
                findWords(board, i, j + 1, p, word, result);//上边
                board.at(i).at(j) = ch;
                word.pop_back();
            }
        }
  • 相关阅读:
    使用客户端模型编程处理外部列表
    在 SharePoint2010 中使用 XML 查看器取得其他站点的内容列表
    在 SharePoint2010 中编程方式使用 BCS
    使用 zip 压缩包手动安装 Orchard
    NuGet Action Plan 更新到 1.1, 设置自动更新,获取 NuGet 包浏览器
    Percentage Closer Filtering
    Direct3D轮回:游戏特效之全屏泛光(Bloom)
    高斯模糊
    Shadow Map
    像素级动态模糊(Pixel Motion Blur)
  • 原文地址:https://www.cnblogs.com/yeqluofwupheng/p/6793195.html
Copyright © 2011-2022 走看看