【数据结构与算法之美学习】Trie树:如何实现搜索引擎的搜索关键词提示功能?

本文详细介绍了Trie树,一种用于高效字符串匹配的数据结构。Trie树通过合并公共前缀来减少存储空间,常用于搜索引擎的关键词提示功能。文章阐述了Trie树的构造过程,即逐个插入字符串,以及如何通过字符映射数组存储节点。同时,解释了查找操作,通过匹配字符路径来确定字符串是否存在。最后,提供了Trie树节点的Java实现及插入和查找方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.Trie树的介绍

搜索引擎的搜索关键词提示功能,它底层使用的是Trie树。

Trie树,也叫“字典树”。顾名思义,它是一个树形结构。它是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。
假设我们有6个字符串,它们分别是:how,hi,her,hello,so,see。我们希望在里面多次查找某个字符串是否存在。

Trie树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起。最后构造出来的就是下面这个图中的样子。
Trie树
其中,根节点不包含任何信息。每个节点表示一个字符串中的字符,从根节点到红色节点的一条路径表示一个字符串(注意:红色节点并不都是叶子节点)。

二.Trie树是怎么构造出来的?

Trie树构造的分解过程如下。构造过程的每一步,都相当于往Trie树中插入一个字符串。当所有字符串都插入完成之后,Trie树就构造好了。
在这里插入图片描述

当我们在Trie树中查找一个字符串的时候,比如查找字符串“her”,那我们将要查找的字符串分割成单个的字符h,e,r,然后从Trie树的根节点开始匹配。如图所示,绿色的路径就是在Trie树中匹配的路径。
查找
如果我们要查找的是字符串“he”呢?我们还用上面同样的方法,从根节点开始,沿着某条路径来匹配,如图所示,绿色的路径,是字符串“he”匹配的路径。但是,路径的最后一个节点“e”并不是红色的。也就是说,“he”是某个字符串的前缀子串,但并不能完全匹配任何字符串。

三.如何实现一棵Trie树?

从刚刚Trie树的介绍来看,Trie树主要有两个操作,一个是将字符串集合构造成Trie树。这个过程分解开来的话,就是一个将字符串插入到Trie树的过程。另一个是在Trie树中查询一个字符串。

了解了Trie树的两个主要操作之后,我们再来看下,如何存储一个Trie树?

从前面的图中,我们可以看出,Trie树是一个多叉树。我们知道,二叉树中,一个节点的左右子节点是通过两个指针来存储的,如下所示Java代码。那对于多叉树来说,我们怎么存储一个节点的所有子节点的指针呢?

借助散列表的思想,我们通过一个下标与字符一一映射的数组,来存储子节点的指针。

由于字母只有26个,所以可以建一个长度为26的数组,将a-z映射到数组的下标0-25里。

假设我们的字符串中只有从a到z这26个小写字母,我们在数组中下标为0的位置,存储指向子节点a的指针,下标为1的位置存储指向子节点b的指针,以此类推,下标为25的位置,存储的是指向的子节点z的指针。如果某个字符的子节点不存在,我们就在对应的下标的位置存储null。

class TrieNode {
  char data;
  TrieNode children[26];
}

当我们在Trie树中查找字符串的时候,我们就可以通过字符的ASCII码减去“a”的ASCII码,迅速找到匹配的子节点的指针。比如,d的ASCII码减去a的ASCII码就是3,那子节点d的指针就存储在数组中下标为3的位置中。

/**
 * 树节点
 */
public class TrieNode {
    // 值
    public char data;
    // 子节点 通过一个下标与字符一一映射的数组,来存储子节点的指针 下标0-25映射的子节点a-z
    public TrieNode[] children = new TrieNode[26];
    // 是否是最后一个字符
    public boolean isEndingChar = false;

    public TrieNode(char data) {
        this.data = data;
    }
}

构建与查找操作

/**
 * 实现一棵Trie树
 *
 * 构建Trie树的过程,需要扫描所有的字符串,时间复杂度是O(n)(n表示所有字符串的长度和)。
 * 每次查询时,如果要查询的字符串长度是k,那我们只需要比对大约k个节点,就能完成查询操作。
 * 构建好Trie树后,在其中查找字符串的时间复杂度是O(k),k表示要查找的字符串的长度。
 */
public class Trie {
    // 存储无意义字符
    TrieNode root = new TrieNode('/');

    // 往Trie树中插入一个字符串
    public void insert(char[] text) {
        TrieNode p = root;
        for (char c : text) {
            int index = c - 'a';
            if (p.children[index] == null) {
                TrieNode newNode = new TrieNode(c);
                p.children[index] = newNode;
            }
            p = p.children[index];
        }
        p.isEndingChar = true;
    }

    // 在Trie树中查找一个字符串
    public boolean find(char[] pattern) {
        TrieNode p = root;
        for (char c : pattern) {
            int index = c - 'a';
            if (p.children[index] == null) {
                return false; // 不存在pattern
            }
            p = p.children[index];
        }

        // 不能完全匹配,只是前缀
        return p.isEndingChar;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值