Trie 树

简介 #

Trie 树又叫「字典树」,能够像字典一样录入和查询多个字符串.

构造 #

一般我们会用数组保存字符串,可是这么做既浪费空间,查询速度又慢.

const int N = 4;
const string str[N] = {
    "his", "her", "hello", "this"
};

bool query(string s) { // 查询 str[] 中是否有字符串 s
    for(int i = 0; i < N; i ++)
        if(str[i] == s) return true;
    return false;
}

如果将字符串放在链表里,就会有相当一部分节点可以合并.例如 hisherhello 的开头都是 h,那么它们可以共享同一个 h 节点.同理,herhello 可以共享 he.

最后,在上方建一个空节点,指向各个字符串的开头,一棵标准的 Trie 树就建好了.至于这个空节点,纯粹是为了让程序写起来更方便.

节点 #

Trie 树的节点存储于结构体中:

const int N = 1e6;
struct Node {
    bool isEnd = false; // 该节点是否为某单词的末尾,默认为 false
    int next[26];       // 该节点的子节点
} trie[N];

按照节点的创建顺序为其编号,令 trie[u] 表示第 $u$ 个建立的节点.trie[u].next['s'] 表示 $u$ 号节点的子节点中,代表字符 $s$ 的节点的编号.

trie[1].next['d'] = 2
trie[1].next['h'] = 3

在程序中,我们一般用 0 - 25 替换 a - z,数组就只要开到 next[26].

当然,也有人习惯于用二维数组存储 Trie 树节点.

插入 #

如何往 Trie 树中插入一个字符串?

本着勤俭节约的精神,能共享的节点就尽量共享.例如要在刚才那棵 Trie 树中插入 thus,并且发现 th 可以共享,那么就只要新建 us 两个节点.

const int N = 1e6;
int tot;
struct Node {
    bool isEnd = false;
    int next[26];
} trie[N];

void insert(string str) { // 往 trie 树中插入字符串 str
    int p = 0; // 最上方的空节点编号为 0
    for(int i = 0; i < str.size(); i ++) {
        int ch = str[i] - 'a';
        if(!trie[p].next[ch]) // 如果不能共享,就新建节点
            trie[p].next[ch] = ++ tot;
        p = trie[p].next[ch];
    }
    trie[p].isEnd = true; // 标记字符串结尾
}

查询 #

查询 Trie 树中是否有某个字符串,只需要从空节点向下搜索即可.

bool search(string str) { // 查询 trie 树中是否有字符串 str
    int p = 0;
    for(int i = 0; i < str.size(); i ++) {
        int ch = str[i] - 'a';
        if(!trie[p].next[ch])
            return false;
        p = trie[p].next[ch];
    }
    return trie[p].isEnd; // 如果想查询 str 是否为前缀,直接返回 true
}

模板 #

const int N = 1e6;
int tot;
struct Node {
    bool isEnd = false;
    int next[26];
} trie[N];

void insert(string str) {
    int p = 0;
    for(int i = 0; i < str.size(); i ++) {
        int ch = str[i] - 'a';
        if(!trie[p].next[ch])
            trie[p].next[ch] = ++ tot;
        p = trie[p].next[ch];
    }
    trie[p].isEnd = true;
}

bool search(string str) {
    int p = 0;
    for(int i = 0; i < str.size(); i ++) {
        int ch = str[i] - 'a';
        if(!trie[p].next[ch])
            return false;
        p = trie[p].next[ch];
    }
    return trie[p].isEnd;
}
const int N = 1e6;
int trie[N][26], tot;
bool isEnd[N];

void insert(string str) {
    int p = 0;
    for(int i = 0; i < str.size(); i ++) {
        int ch = str[i] - 'a';
        if(!trie[p][ch])
            trie[p][ch] = ++ tot;
        p = trie[p][ch];
    }
    isEnd[p] = true;
}

bool search(string str) {
    int p = 0;
    for(int i = 0; i < str.size(); i ++) {
        int ch = str[i] - 'a';
        if(!trie[p][ch])
            return false;
        p = trie[p][ch];
    }
    return isEnd[p];
}