Trie(字典树/前缀树)

这段代码实现了一个标准且高效的 Trie(字典树/前缀树) 。它的核心逻辑是利用 数组下标 来代表字符,通过 指针跳转 来表示字符的连接。


1. 核心设计思路

  • 公共前缀的共享:如果两个单词有共同的前缀(比如 "apple" 和 "apply"),它们在树中会共用前面的节点 "a"->"p"->"p"->"l",只在最后一位分叉。

  • 字符不存储在节点内 :这是一个巧妙的点。字母 a-z 并不存在变量里,而是隐含在 next[26] 数组的下标 中。如果 next[0] 不为空,就代表存在字符 'a'。

  • 状态标记isEnd 就像一个终点路标,标记从根节点走到这里是否构成了一个完整的单词,而不仅仅是某个单词的前缀。


2. 代码逐行注释

C++

cpp 复制代码
class Trie {
private:
    bool isEnd;        // 标记:当前节点是否为一个单词的结束位置
    Trie* next[26];    // 指针数组:存储指向 26 个字母子节点的指针

public:
    // --- 构造函数:初始化节点 ---
    Trie() {
        isEnd = false;               // 初始化为非结尾状态
        memset(next, 0, sizeof(next)); // 将 26 个子节点指针全部置为 NULL (0)
    }
    
    // --- 插入操作:向字典树添加一个单词 ---
    void insert(string word) {
        Trie* node = this;           // 从当前根节点开始遍历
        for (char c : word) {        // 遍历单词的每个字符
            // 如果字符对应的子节点不存在 (NULL)
            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;
            }
        }
        // 路径走通了,但还要检查该节点是否是一个单词的终点 (isEnd)
        // 比如存了 "apple",搜 "app" 虽然路径通,但 "p" 不是终点,返回 false
        return node->isEnd;
    }
    
    // --- 前缀查找:判断是否有单词以 prefix 开头 ---
    bool startsWith(string prefix) {
        Trie* node = this;
        for (char c : prefix) {
            node = node->next[c-'a']; // 沿着前缀路径往下走
            if (node == NULL) {       // 只要路径中途断开,说明没有以此为开头的单词
                return false;
            }
        }
        // 只要路径能全部走完,就说明前缀存在(不需要判断 isEnd)
        return true;
    }
};

3. 运行逻辑图解

假设我们执行:insert("app"), insert("apple")

  1. 插入 "app"

    • 创建 a -> p -> p 三个节点。

    • 在第二个 p 节点打上标签 isEnd = true

  2. 插入 "apple"

    • 发现 a, p, p 已存在,直接沿用。

    • 创建新节点 l -> e

    • e 节点打上标签 isEnd = true

  3. 查询 "app"

    • 路径走通了,且 isEndtrue,返回 true
  4. 查询 "ap"

    • 路径走通了,但 isEndfalse(因为没有存过 "ap" 这个词),search 返回 false,但 startsWith 会返回 true

4. 复杂度分析

  • 时间复杂度

    • insert: O(L),其中 L 是单词长度。

    • search: O(L)

    • startsWith: O(L)

    • 注:与树中存了多少个单词无关,只跟当前查的单词长度有关,这就是 Trie 强大的原因。

  • 空间复杂度O(N \\times 26)N 是所有单词的节点总数。每个节点都要预留 26 个指针位,比较耗内存。

  • 前缀树 数据结构 next是一个数组

相关推荐
似水明俊德2 小时前
15-C#
android·开发语言·c#
hua872222 小时前
Golang 构建学习
java·开发语言·学习
2301_803554522 小时前
qt信号槽机制以及底层实现原理
开发语言·qt
大傻^2 小时前
LangChain4j RAG 核心:Document、Embedding 与向量存储抽象
开发语言·人工智能·python·embedding·langchain4j
笨笨马甲2 小时前
Qt 音视频编解码
开发语言·qt
Halo_tjn2 小时前
Java 三个修饰符 相关知识点
java·开发语言
2401_883035462 小时前
C++20概念(Concepts)入门指南
开发语言·c++·算法
番茄去哪了2 小时前
Java基础面试题day01
java·开发语言·后端·javase·八股·面向对象编程
wuqingshun3141592 小时前
说说进程和线程的区别?
java·开发语言·jvm