LeetCodehot 力扣热题100 实现 Trie (前缀树)

class Trie {
private:
    bool isEnd;
    Trie* next[26];
public:
    Trie() {
        isEnd = false;
        memset(next, 0, sizeof(next));
    }
    
    void insert(string word) {
        Trie* node = this;
        for (char c : word) {
            if (node->next[c-'a'] == NULL) {
                node->next[c-'a'] = new Trie();
            }
            node = node->next[c-'a'];
        }
        node->isEnd = true;
    }
    
    bool search(string word) {
        Trie* node = this;
        for (char c : word) {
            node = node->next[c - 'a'];
            if (node == NULL) {
                return false;
            }
        }
        return node->isEnd;
    }
    
    bool startsWith(string prefix) {
        Trie* node = this;
        for (char c : prefix) {
            node = node->next[c-'a'];
            if (node == NULL) {
                return false;
            }
        }
        return true;
    }
};

这是一个 Trie (前缀树) 的实现,常用于高效的单词查找、前缀匹配等场景。下面是每一行的注释和详细思路。

作者链接:208. 实现 Trie (前缀树) - 力扣(LeetCode)

1. 类定义

class Trie {

private:

    bool isEnd;                  // 标记当前节点是否为一个单词的结尾

    Trie* next[26];              // 存储子节点,最多26个字母

• isEnd 用于标记当前节点是否是一个有效单词的结尾。

• next[26] 是一个数组,用来存储当前节点的所有子节点。数组的大小为 26,代表英文字母 ‘a’ 到 ‘z’。每个子节点指向一个 Trie 对象。

2. 构造函数

public:

    Trie() {

        isEnd = false;                  // 初始化时,当前节点不是单词的结尾

        memset(next, 0, sizeof(next));  // 初始化所有子节点指针为 NULL

    }

• 构造函数将 isEnd 设置为 false(表示当前节点不是单词的结尾)。

• memset(next, 0, sizeof(next)) 将 next 数组的每个元素(即每个子节点指针)初始化为 NULL。

3. 插入单词

    void insert(string word) {

        Trie* node = this;                // 从当前根节点开始插入

        for (char c : word) {             // 遍历单词中的每个字符

            if (node->next[c-'a'] == NULL) {  // 如果该字母的子节点为空

                node->next[c-'a'] = new Trie();  // 创建新的 Trie 节点

            }

            node = node->next[c-'a'];      // 向下移动到该字符对应的子节点

        }

        node->isEnd = true;                // 最后一个字符的节点标记为单词的结尾

    }

• insert 方法用来插入一个单词。

• node = this; 从根节点开始。

• 对于单词中的每个字符:

• 计算该字符对应的数组索引(c - 'a'),检查 next 数组中是否存在该字符对应的子节点。

• 如果子节点不存在,则创建一个新的 Trie 节点。

• 移动 node 指针到该子节点。

• 最后,将 node->isEnd = true; 标记当前节点为单词的结束位置。

4. 查找单词

    bool search(string word) {

        Trie* node = this;                // 从根节点开始查找

        for (char c : word) {             // 遍历单词中的每个字符

            node = node->next[c - 'a'];   // 移动到该字符对应的子节点

            if (node == NULL) {           // 如果某个字符对应的子节点不存在

                return false;             // 返回 false,说明单词不存在

            }

        }

        return node->isEnd;               // 最后检查当前节点是否为一个单词的结尾

    }

• search 方法用来查找是否存在某个单词。

• 从根节点开始,遍历单词中的每个字符。

• 对每个字符,计算索引 c - 'a',并检查是否有对应的子节点。

• 如果没有对应的子节点,返回 false,表示单词不存在。

• 最后,检查当前节点是否是一个单词的结尾。如果是,返回 true,否则返回 false。

5. 查找前缀

    bool startsWith(string prefix) {

        Trie* node = this;                // 从根节点开始查找

        for (char c : prefix) {           // 遍历前缀中的每个字符

            node = node->next[c - 'a'];   // 移动到该字符对应的子节点

            if (node == NULL) {           // 如果某个字符对应的子节点不存在

                return false;             // 返回 false,表示没有该前缀

            }

        }

        return true;                       // 如果所有字符都有对应的子节点,则说明有这个前缀

    }

};

• startsWith 方法用来判断是否存在某个前缀。

• 从根节点开始,遍历前缀中的每个字符。

• 对每个字符,计算索引 c - 'a',并检查是否有对应的子节点。

• 如果没有对应的子节点,返回 false,表示没有这个前缀。

• 如果所有字符都有对应的子节点,返回 true,表示该前缀存在。

好的,让我们通过一个详细的示例来演示这个 Trie 树的操作。假设我们有以下操作:

1. 插入单词 “apple”

2. 插入单词 “app”

3. 查找单词 “apple”

4. 查找单词 “app”

5. 查找前缀 “app”

假设 Trie 树初始化为空

1. 插入单词 “apple”

步骤:

• 我们从根节点开始 (node = this)。

• 对于 “apple” 中的每个字符,逐个插入:

插入 “a”

• 计算索引 c - 'a' = 'a' - 'a' = 0。

• 检查 next[0],发现为空 (NULL),创建一个新的 Trie 节点。

• 将 node 移动到新创建的节点。

插入 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],为空,创建一个新的 Trie 节点。

• 将 node 移动到新创建的节点。

插入 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],发现已经存在对应的节点,直接移动到该节点。

插入 “l”

• 计算索引 c - 'a' = 'l' - 'a' = 11。

• 检查 next[11],为空,创建一个新的 Trie 节点。

• 将 node 移动到新创建的节点。

插入 “e”

• 计算索引 c - 'a' = 'e' - 'a' = 4。

• 检查 next[4],为空,创建一个新的 Trie 节点。

• 将 node 移动到新创建的节点。

• 到这里,“apple” 的所有字符都插入完毕,我们将最后一个节点的 isEnd 标记为 true,表示这是一个完整的单词。

2. 插入单词 “app”

步骤:

• 我们从根节点开始 (node = this)。

• 对于 “app” 中的每个字符,逐个插入:

插入 “a”

• 计算索引 c - 'a' = 'a' - 'a' = 0。

• 检查 next[0],已经存在对应的节点,直接移动到该节点。

插入 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],已经存在对应的节点,直接移动到该节点。

插入 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],已经存在对应的节点,直接移动到该节点。

• 到这里,“app” 的所有字符都已经插入了。我们将最后一个节点的 isEnd 标记为 true,表示 “app” 也是一个完整的单词。

3. 查找单词 “apple”

步骤:

• 我们从根节点开始 (node = this)。

• 对于 “apple” 中的每个字符,逐个查找:

查找 “a”

• 计算索引 c - 'a' = 'a' - 'a' = 0。

• 检查 next[0],存在节点,继续查找。

查找 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],存在节点,继续查找。

查找 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],存在节点,继续查找。

查找 “l”

• 计算索引 c - 'a' = 'l' - 'a' = 11。

• 检查 next[11],存在节点,继续查找。

查找 “e”

• 计算索引 c - 'a' = 'e' - 'a' = 4。

• 检查 next[4],存在节点,继续查找。

• 最后,我们到达单词 “apple” 的结尾节点,并且 isEnd = true,说明单词 “apple” 存在,返回 true。

4. 查找单词 “app”

步骤:

• 我们从根节点开始 (node = this)。

• 对于 “app” 中的每个字符,逐个查找:

查找 “a”

• 计算索引 c - 'a' = 'a' - 'a' = 0。

• 检查 next[0],存在节点,继续查找。

查找 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],存在节点,继续查找。

查找 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],存在节点,继续查找。

• 最后,我们到达 “app” 的结尾节点,并且 isEnd = true,说明单词 “app” 存在,返回 true。

5. 查找前缀 “app”

步骤:

• 我们从根节点开始 (node = this)。

• 对于前缀 “app” 中的每个字符,逐个查找:

查找 “a”

• 计算索引 c - 'a' = 'a' - 'a' = 0。

• 检查 next[0],存在节点,继续查找。

查找 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],存在节点,继续查找。

查找 “p”

• 计算索引 c - 'a' = 'p' - 'a' = 15。

• 检查 next[15],存在节点,继续查找。

• 最后,所有字符都有对应的节点,说明有前缀 “app”,返回 true。

总结:

插入:每次插入一个单词时,我们会遍历该单词的每个字符,并在 Trie 树中逐步创建新的节点或沿用已有的节点。

查找单词:通过遍历单词的每个字符,检查是否有对应的节点,最终判断该单词是否结束。

查找前缀:与查找单词类似,但我们不关心节点是否是单词的结尾,只关心是否存在这个前缀。

每个操作的时间复杂度是 O(m),其中 m 是单词或前缀的长度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值