字典树又称前缀树,是一种专门用于高效存储和检索字符串集合的树形数据结构,其核心思想是利用字符串的公共前缀来减少存储空间并加速查询:每个节点代表一个字符,从根节点到任意节点的路径构成一个字符串前缀,通过共享前缀分支来优化存储,并支持快速的插入、查找和前缀匹配操作,广泛应用于搜索引擎自动补全、拼写检查和IP路由等场景。本节的代码模板将向您展示如何通过数组模拟来实现字典树。

数组模拟的特点是内存管理高效、实现简单、性能稳定,适用于只需基本插入查询的场景。相较于链表模拟实现字典树来说,其缺点也可见一斑:存在功能有限、内存固定、不支持删除和重复字符串,扩展性差等问题。


1. C/C++版代码:

// 数组模拟字典树
#include <iostream>
#include <string>
using namespace std;
const int MAXN = 26;      // 字符集大小:仅支持26个小写字母
const int MAXM = 100000;  // 最大节点数:限制字典树容量防止越界
int trie[MAXM][MAXN];     // 字典树主体:trie[p][c]表示节点p通过字符c到达的子节点编号
bool end[MAXM];           // 结束标志:end[p]=true表示节点p是某个单词的结尾
int cnt = 1;              // 已使用节点数:从1开始分配,0作为根节点(空串)
// 插入字符串到字典树
void insert(const string& s) {
    int p = 0;                        // 从根节点(节点0)开始遍历
    for (char ch : s) {
        int c = ch - 'a';             // 将字符映射到0-25的索引
        if (!trie[p][c]) {            // 如果当前字符对应的子节点不存在
            if (cnt >= MAXM) return;  // 容量检查:超出最大节点数则提前返回
            trie[p][c] = cnt++;       // 分配新节点:将下一个可用节点编号赋给当前路径
        }
        p = trie[p][c];               // 移动到子节点:继续处理下一个字符
    }
    end[p] = true;                    // 标记终点:字符串最后一个字符所在的节点为单词结尾
}
// 查询字符串是否存在于字典树
bool search(const string& s) {
    int p = 0;                        // 从根节点开始遍历
    for (char ch : s) {
        int c = ch - 'a';             // 字符映射
        if (!trie[p][c]) return false; // 路径中断:某个字符对应的节点不存在,字符串不存在
        p = trie[p][c];               // 移动到子节点
    }
    return end[p];                    // 路径完整:检查终点节点是否被标记为单词结尾
}


2. Java版代码:

// 数组模拟字典树
public class ArrayTrie {
    // 字符集大小:仅支持26个小写字母
    private static final int MAXN = 26;
    // 最大节点数:限制字典树容量防止越界
    private static final int MAXM = 100000;
    // 字典树主体:trie[p][c]表示节点p通过字符c到达的子节点编号
    private static int[][] trie = new int[MAXM][MAXN];
    // 结束标志:end[p]=true表示节点p是某个单词的结尾
    private static boolean[] end = new boolean[MAXM];
    // 已使用节点数:从1开始分配,0作为根节点(空串)
    private static int cnt = 1;
    
    // 插入字符串到字典树
    public static void insert(String s) {
        int p = 0;                        // 从根节点(节点0)开始遍历
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            int c = ch - 'a';             // 将字符映射到0-25的索引
            if (trie[p][c] == 0) {        // 如果当前字符对应的子节点不存在
                if (cnt >= MAXM) return;  // 容量检查:超出最大节点数则提前返回
                trie[p][c] = cnt++;       // 分配新节点:将下一个可用节点编号赋给当前路径
            }
            p = trie[p][c];               // 移动到子节点:继续处理下一个字符
        }
        end[p] = true;                    // 标记终点:字符串最后一个字符所在的节点为单词结尾
    }
    
    // 查询字符串是否存在于字典树
    public static boolean search(String s) {
        int p = 0;                        // 从根节点开始遍历
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            int c = ch - 'a';             // 字符映射
            if (trie[p][c] == 0) return false; // 路径中断:某个字符对应的节点不存在,字符串不存在
            p = trie[p][c];               // 移动到子节点
        }
        return end[p];                    // 路径完整:检查终点节点是否被标记为单词结尾
    }
    
    // 主函数
    public static void main(String[] args) {
    // 主函数
    }
}


3. Python版代码:

# 数组模拟字典树
# 字符集大小:仅支持26个小写字母
MAXN = 26
# 最大节点数:限制字典树容量防止越界
MAXM = 100000
# 字典树主体:trie[p][c]表示节点p通过字符c到达的子节点编号
trie = [[0] * MAXN for _ in range(MAXM)]
# 结束标志:end[p]=True表示节点p是某个单词的结尾
end = [False] * MAXM
# 已使用节点数:从1开始分配,0作为根节点(空串)
cnt = 1
# 插入字符串到字典树
def insert(s):
    global cnt
    p = 0                        # 从根节点(节点0)开始遍历
    for ch in s:
        c = ord(ch) - ord('a')   # 将字符映射到0-25的索引
        if trie[p][c] == 0:      # 如果当前字符对应的子节点不存在
            if cnt >= MAXM:      # 容量检查:超出最大节点数则提前返回
                return
            trie[p][c] = cnt     # 分配新节点:将下一个可用节点编号赋给当前路径
            cnt += 1
        p = trie[p][c]           # 移动到子节点:继续处理下一个字符
    end[p] = True               # 标记终点:字符串最后一个字符所在的节点为单词结尾
# 查询字符串是否存在于字典树
def search(s):
    p = 0                        # 从根节点开始遍历
    for ch in s:
        c = ord(ch) - ord('a')   # 字符映射
        if trie[p][c] == 0:      # 路径中断:某个字符对应的节点不存在,字符串不存在
            return False
        p = trie[p][c]           # 移动到子节点
    return end[p]               # 路径完整:检查终点节点是否被标记为单词结尾
# 主函数
def main():
    pass  # 主函数
if __name__ == "__main__":
    main()
点赞(0)

C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:

一点编程也不会写的:零基础C语言学练课程

解决困扰你多年的C语言疑难杂症特性的C语言进阶课程

从零到写出一个爬虫的Python编程课程

只会语法写不出代码?手把手带你写100个编程真题的编程百练课程

信息学奥赛或C++选手的 必学C++课程

蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程

手把手讲解近五年真题的蓝桥杯辅导课程

Dotcpp在线编译      (登录可减少运行等待时间)