[Trie树板子]

Trie树

Trie树也叫字典树。

主要是用于多个字符串中与某个字符串的匹配,也是AC自动机的一个基础。

Trie树的好处在于匹配时只需要O(N)算法即可完成,如果使用暴力则需要O(MN),这就是一种优化。

Trie树的实现

一般是利用多个数组来模拟树的链接关系。

为了简单起见,我们只考虑小写字母的字典树。

为了建立树上父节点和子节点的关系,我们在每个父节点都给预留26个子节点空间,当存在值为x的子节点时,我们就是使得该子节点赋予一个非零的索引值,表示其在数组中占据一个有效位置。

我们一般使得树的根节点占据节点。

假设最多只有1000个字符串,每个字符串至多100个。因此我们只需要1000 * 100* 26个空间;

const int N = 1000 * 100 + 10; //多开一点空间;
int tr[N][26];
int idx = 0;
int cnt[N]; //用来标记在哪个节点处是一个字符串的结尾;

需要注意的是,在Trie树中,边表示的是哪个节点。

比如起始,我们在构建Trie树时,我们要插入"abc"这个字符串。

当插入第一个字符‘a’时,因为起始所有指向都是0,表示没有这样的边,因此需要为其开个有效节点,并更新当前节点。

依次类推,当添加完'c',我们在 p= 3这个节点处标记一下,这是一个字符串的结尾。

当添加其他字符串时,思路类似,在图中的基础上继续这么添加即可。

实现代码如下:

void insert(string str)
{
    int p = 0; //从根节点开始遍历;
    for (int i = 0; i < str.size(); i++) 
    {
        char c = str[i];
        if (!tr[p][c]) { //如果这样节点不存在,那么我们需要给其创建一个;
            tr[p][c] = ++idx;     
        }
        p = tr[p][c]; //将当前节点往下更新;
    }
    cnt[p]++; //表示以该节点结尾是某些字符串的尾部;
}

实现了构建insert()接口后,query()接口也好实现:

bool query(string str)
{
    int p = 0;
    for (int i = 0; i < str.size(); i++)
    {
        char c = str[i];
        if (!tr[p][c]) return false; //没有该节点则说明当前边已经有所不同;
        p = tr[p][c];
    }
    
    return true;
}

有了这两个之后,我们就可以做一些模板题了:

[LC676]:
static int son[10010][26];
int idx = 0;
static int cnt[10010];
class MagicDictionary {
public:
    //Trie树相关的数据;
    MagicDictionary() {
        memset(son, 0, sizeof son);
        memset(cnt, 0, sizeof cnt);
        idx = 0;
    }

    void insert(string& str)
    {
        int p = 0;
        for (int i = 0; i < str.size(); i++)
        {
            int u = str[i] - 'a';
            if (!son[p][u]) son[p][u] = ++idx;
            p = son[p][u];
        }

        cnt[p]++; //标记字符串结尾;
    }

    void buildDict(vector<string> dictionary) {
        for (auto str : dictionary)
        {
            insert(str);
        }
    }
    
    bool search(string searchWord) {
        return query(searchWord);
    }

    //对于板子中的query进行适当改造,不是在第一次失配就返回,而是继续向下搜索,同时记录当前搜索过程中失配次数;
    //当遍历到字符串结束时,且失配次数满足题目要求,说明这是一个符合要求的字符串;
    bool query(string & str)
    {
        int nums = 0; //表示在当前Trie树搜索中对应的不同字母个数;
        int p = 0; //进行dfs遍历查找;
        return dfs(str, p,  nums, 0);
    }

    bool dfs(string& str, int p, int nums, int idx)
    {
        if (idx == str.size())
        {
            if (nums == 1 && cnt[p])
                return true; 
            return false;
        }

        //遍历该Trie树上所有分支;
        for (int i = 0; i < 26; i++)
        {
            if (!son[p][i]) continue;
            if (dfs(str, son[p][i], nums + ((str[idx] - 'a') != i) ,idx+1)) return true;
        }
        return false;
    }
};

/**
 * Your MagicDictionary object will be instantiated and called as such:
 * MagicDictionary* obj = new MagicDictionary();
 * obj->buildDict(dictionary);
 * bool param_2 = obj->search(searchWord);
 */

有了上面这些基础,我们可以更近一步做些技巧性更强的题目了。

练习题:

  1. https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/
  2. https://leetcode.cn/problems/maximum-xor-with-an-element-from-array/
  3. https://leetcode.cn/problems/replace-words/
posted @   zhanghanLeo  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示