算法题解记录-208实现Trie前缀树

【前缀树 (Trie)】详解与实现

一、问题理解

前缀树 (Trie) 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。它的核心特点是:

  • 每个节点表示一个字符
  • 从根节点到某一节点的路径上的字符连接起来,构成该节点对应的字符串
  • 常用于自动补全、拼写检查、字典查询等场景

本题要求实现一个 Trie 类,具备以下三个核心功能:

  1. insert(String word) → 插入一个单词
  2. search(String word) → 查找一个完整的单词是否存在
  3. startsWith(String prefix) → 查找是否存在以某个前缀开头的单词

二、数据结构设计

前缀树的核心在于节点设计。每个节点需要:

  1. 一个布尔标记 isEnd:表示从根节点到当前节点的路径是否构成一个完整的单词。
  2. 一个子节点数组 children:用于存储下一个可能出现的字符(通常长度为 26,对应 26 个小写英文字母)。
java 复制代码
class TrieNode {
    boolean isEnd;
    TrieNode[] children;
    
    public TrieNode() {
        isEnd = false;
        children = new TrieNode[26]; // 默认每个元素为 null
    }
}

初始化时

  • 创建根节点 root,它是一个虚拟节点,不存储实际字符。
  • isEnd 初始为 false
  • children 数组初始化为 null 数组,表示还没有任何子节点。

三、方法实现详解

1. 插入操作 insert(String word)

插入一个单词时,从根节点出发,依次处理每个字符:

  1. 计算字符在 children 数组中的索引:index = ch - 'a'
  2. 如果当前节点没有对应的子节点,则新建一个节点
  3. 移动到子节点,继续处理下一个字符
  4. 处理完最后一个字符后,将当前节点的 isEnd 标记为 true
java 复制代码
public void insert(String word) {
    TrieNode node = root;
    for (char ch : word.toCharArray()) {
        int index = ch - 'a';
        if (node.children[index] == null) {
            node.children[index] = new TrieNode();
        }
        node = node.children[index];
    }
    node.isEnd = true;
}

2. 查找完整单词 search(String word)

查找一个完整单词的过程与插入类似,但有两个关键区别:

  1. 如果路径中某个字符对应的子节点不存在,直接返回 false
  2. 遍历到最后一个字符后,需要检查该节点的 isEnd 是否为 true
java 复制代码
public boolean search(String word) {
    TrieNode node = root;
    for (char ch : word.toCharArray()) {
        int index = ch - 'a';
        if (node.children[index] == null) {
            return false;
        }
        node = node.children[index];
    }
    return node.isEnd;
}

3. 查找前缀 startsWith(String prefix)

查找前缀的过程与 search 类似,但不需要检查 isEnd

  • 只要路径上的所有字符都存在对应的子节点,就说明存在以该前缀开头的单词
java 复制代码
public boolean startsWith(String prefix) {
    TrieNode node = root;
    for (char ch : prefix.toCharArray()) {
        int index = ch - 'a';
        if (node.children[index] == null) {
            return false;
        }
        node = node.children[index];
    }
    return true;
}

四、示例演示

假设我们要依次插入以下单词:

  • "apple"
  • "app"

插入过程

复制代码
插入 "apple":
root -> a -> p -> p -> l -> e (isEnd = true)

插入 "app":
root -> a -> p -> p (isEnd = true)

查询过程

  • search("app") → 返回 true(因为 app 是一个完整单词)
  • search("ap") → 返回 false(因为路径存在,但 isEndfalse
  • startsWith("ap") → 返回 true(因为存在以 ap 开头的单词)
  • startsWith("appl") → 返回 true(因为 apple 以此开头)

五、总结与心得

核心思路:

  • 节点设计是关键isEnd 标记 + children 数组
  • 根节点是虚拟节点:不存储字符,只作为起点
  • 插入时建路径:按字符逐层创建或复用节点
  • 查找时走路径 :路径存在 + isEnd 标记决定结果

记忆点:

  1. 节点结构固定:boolean isEnd + TrieNode[26] children
  2. 插入末尾要标记 isEnd = true
  3. 查找完整单词时要检查 isEnd
  4. 查找前缀时只需路径存在即可

为什么归为"图论"?

虽然 Trie 本质是一棵树,但它可以看作一种有向图(节点为状态,边为字符转移)。在算法题分类中,有时将树视为特殊的图,因此将 Trie 归入图论范畴。


六、完整代码模板(Java)

java 复制代码
class Trie {
    class TrieNode {
        boolean isEnd;
        TrieNode[] children;
        public TrieNode() {
            isEnd = false;
            children = new TrieNode[26];
        }
    }
    
    private TrieNode root;
    
    public Trie() {
        root = new TrieNode();
    }
    
    public void insert(String word) {
        TrieNode node = root;
        for (char ch : word.toCharArray()) {
            int index = ch - 'a';
            if (node.children[index] == null) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
        }
        node.isEnd = true;
    }
    
    public boolean search(String word) {
        TrieNode node = root;
        for (char ch : word.toCharArray()) {
            int index = ch - 'a';
            if (node.children[index] == null) {
                return false;
            }
            node = node.children[index];
        }
        return node.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        TrieNode node = root;
        for (char ch : prefix.toCharArray()) {
            int index = ch - 'a';
            if (node.children[index] == null) {
                return false;
            }
            node = node.children[index];
        }
        return true;
    }
}

学会了 Trie,你就掌握了一种高效处理字符串集合的工具。它不仅是一个经典的数据结构,更是很多高级算法(如 AC 自动机、后缀树)的基础。多写几次,你会发现它的逻辑其实非常直观!

相关推荐
小天源17 分钟前
nginx在centos7上热升级步骤
linux·服务器·nginx
数研小生1 小时前
构建命令行单词记忆工具:JSON 词库与艾宾浩斯复习算法的完美结合
算法·json
芒克芒克1 小时前
LeetCode 题解:除自身以外数组的乘积
算法·leetcode
AZ996ZA1 小时前
自学linux第十八天:【Linux运维实战】系统性能优化与安全加固精要
linux·运维·安全·性能优化
Python 老手1 小时前
Python while 循环 极简核心讲解
java·python·算法
@Aurora.1 小时前
优选算法【专题九:哈希表】
算法·哈希算法·散列表
爱看科技2 小时前
微美全息(NASDAQ:WIMI)研究拜占庭容错联邦学习算法,数据安全与隐私保护的双重保障
算法
qq_417129252 小时前
C++中的桥接模式变体
开发语言·c++·算法
一体化运维管理平台2 小时前
DevOps落地利器:美信监控易如何打通开发与运维?
运维
YuTaoShao2 小时前
【LeetCode 每日一题】3010. 将数组分成最小总代价的子数组 I——(解法二)排序
算法·leetcode·排序算法