Dictionary前缀搜索时间复杂度为O(N×L),而Trie优化至O(M);TrieNode用Dictionary存子节点、IsEnd标记词尾,支持Unicode;空串插入需设root.IsEnd=true,重复插入不覆盖,未实现Delete时需防内存泄漏。

为什么不用 Dictionary 做前缀搜索
直接用 Dictionary 调用 Keys.Where(k => k.StartsWith(prefix)) 看似简单,但时间复杂度是 O(N×L),N 是键数量,L 是前缀长度。当键量上万、频繁查前缀时,性能明显下降。Trie 的核心价值是把前缀匹配从“遍历所有键”变成“沿路径走一次”,平均耗时接近 O(M),M 是前缀长度。
如何设计 TrieNode 与插入逻辑
每个节点只存一个字符(非整个字符串),用 Dictionary 存子节点,避免固定 26 字母数组(支持 Unicode 和空格等)。关键点是:是否为单词结尾,需单独用布尔字段标记,不能靠子节点为空判断——比如插入 "a" 和 "abc" 时,"a" 必须显式标记为完整词。
-
IsEnd字段必须存在,且插入末尾字符后设为true - 插入时逐字符向下找,子节点不存在就新建;大小写敏感与否,由调用方统一处理(如提前
.ToLower()) - 不建议在节点里存值(如
T value),而是让IsEnd = true的节点对应外部字典的 key,或改用TrieNode泛型承载值
PrefixSearch 返回所有匹配键还是只判存在
实际业务中二者需求都常见:自动补全要返回字符串列表,路由匹配可能只需 StartsWith 判断。Trie 本身更适合高效判断存在性;若要枚举所有以某前缀开头的键,得在找到前缀终点后做 DFS 遍历子树,此时结果顺序不天然有序,需额外排序。
- 存在性检查:走到前缀末节点,检查
IsEnd或该节点是否可达即可,O(M) - 获取全部匹配键:先定位前缀节点,再递归收集所有
IsEnd == true的路径,注意避免在中间节点提前终止(它们可能只是更长词的前缀) - 如果需要按字典序返回,DFS 过程中对
Children.Keys排序;高频场景建议缓存结果或改用SortedDictionary
实际使用时容易漏掉的边界情况
空字符串 "" 是合法前缀,也是合法键。Trie 根节点不对应任何字符,所以插入空串时,必须把根节点的 IsEnd 设为 true。另外,多次插入同一词不应覆盖已有值,但应保持 IsEnd = true 不变;删除操作若未实现,直接清空会导致内存泄漏——尤其在长期运行服务中,节点引用残留可能阻碍 GC。
- 插入
"":不走循环,直接设置root.IsEnd = true - 重复插入相同字符串:不影响结构,但若依赖节点存储频次等元数据,需在终点节点累加而非覆盖
- 没实现
Delete时,别依赖“重新 new Trie()”来重置——旧节点若被闭包或事件引用,仍驻留内存
Insert 和 Search 函数签名里体现,却决定它能不能在线上稳住。










