trie + dfs 实现编辑距离≤1的模糊补全,节点用 unordered_map 支持动态跳转,dfs 并列处理替换、删除、插入三类操作并剪枝,结果用 vector 收集后三级排序去重。

用 Trie + DFS 实现带模糊匹配的补全,不是靠编辑距离硬算
模糊匹配自动补全在 C++ 里真不能直接套 std::string::find 或暴力跑 Levenshtein。实际工程中,90% 的“模糊”指的是错一位、少一位、多一位(即 Damerau-Levenshtein 距离 ≤1),而 Trie 是唯一能兼顾前缀剪枝和局部容错的数据结构。
关键不是“支持模糊”,而是“只查可能模糊的路径”。比如输入 "hel",你得同时走:正常路径 h→e→l,以及所有允许一次替换/插入/删除的分支(如把 e 换成 a,或在 l 后插 p)。
- 每个 Trie 节点加一个
is_word标志和word_count(用于排序优先级) - DFS 搜索时维护当前编辑距离
ed,一旦 >1 就剪枝 - 不预计算所有词对的距离——那会 O(n²),而是边遍历边生成候选
- 插入时不用改结构;模糊补全是纯查询逻辑,不影响建树
Trie 节点怎么设计才能扛住模糊跳转?
标准 Trie 节点只存 children[26] 太死板。模糊操作(比如替换)需要“跳到同层其他字符”,所以必须支持快速枚举所有非空子节点。
推荐用 std::unordered_map<char trienode></char> 替代数组:既省空间,又能让替换操作变成一次 for (auto& [c, child] : node->children) 遍历。
立即学习“C++免费学习笔记(深入)”;
- 插入和普通 Trie 一样,但查询函数签名得带
int max_ed = 1 - 删除操作不用支持模糊逻辑,保持原样即可
- 如果业务要求大小写不敏感,统一转小写插入 + 查询,别在 DFS 里做转换
- 中文或 Unicode?用
std::unordered_map<char32_t ...></char32_t>,但注意char32_t字面量写法是U'中'
DFS 模糊搜索时最容易漏掉的三种情况
很多人只写了“替换”,结果用户输 "helo" 补不出 "hello"(少一个 l),或者输 "hllo" 补不出 "hello"(多一个 l)。这三种编辑操作必须并列处理,且顺序影响剪枝效率。
-
删除:当前输入字符跳过,递归查
node->children中是否含下一个输入字符(即“我删掉这个,后面还能对上吗?”) - 插入:不消耗输入字符,在当前节点尝试所有子节点,看它们的子树能否匹配剩余输入(即“我加一个字符,后面还能对上吗?”)
- 替换:当前字符换成别的,再继续匹配后续——注意要排除原字符,否则等价于无编辑
顺序建议是:先走原路径(ed 不变),再处理替换(ed+1),最后处理插入/删除(ed+1)。这样能尽早命中高相关结果,避免深搜浪费。
性能瓶颈不在 Trie,而在结果去重和排序
一次模糊查询可能从多个路径撞出同一个词(比如 "cat" 可能通过“删 c”+“替 a” 和 “替 c”+“删 a” 两条路到达),而 std::set<:string></:string> 去重会拖慢 3 倍以上。
- 用
std::vector<:pair std::string>></:pair>收集,最后用std::sort+ lambda 按编辑距离、词频、字典序三级排序 - 限制返回数量(如最多 10 个),DFS 中加计数器,满就
return - 如果词表固定,可预生成所有距离为 1 的变形词并插入 Trie(空间换时间),但更新成本高,仅适合静态词典
- Linux 下注意
std::unordered_map的哈希冲突,高频短词场景下,std::map反而更稳
真正难调的是边界:空字符串输入要不要补全?全角空格怎么算?这些不写进 Trie 结构,但会决定你的 search 函数第一行怎么写。










