前缀树-Trie 原理与实现

刷题的时候遇到了利用前缀树来处理字符串包含前缀问题的解法,然后就补充一下前缀树的理解和实现,正好Leetcode208题也是前缀树的实现,作为练手。

原理

“字典树又称前缀树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。”

三个基本性质

  • 根节点不包含字符,除根节点外每个节点只包含一个字符。
  • 从根节点到某个节点,路径上所有的字符连接起来,就是这个节点所对应的字符串。
  • 每个节点的子节点所包含的字符都不同。 

字典树是一种树形结构,优点是利用字符串的公共前缀来节约存储空间。

 字典树如图1所示:

                                             图1

在字典树上搜索添加过的单词的步骤如下:

1、从根结点开始搜索

2、取得要查找单词的第一个字母,并根据该字母选择对应的字符路径向下继续搜索。

3、字符路径指向的第二层节点上,根据第二个字母选择对应的字符路径向下继续搜索。

扫描二维码关注公众号,回复: 7906996 查看本文章

4、一直向下搜索,如果单词搜索完后,找到的最后一个节点是一个终止节点,比如图1中的实心节点,说明字典树中含有这个单词,

如果找到的最后一个节点不是一个终止节点,说明单词不是字典树中添加过的单词。如果单词没搜索完,但是已经没有后续的节点了,

也说明单词不是字典树中添加过的单词。

举个例子:单词leet在Trie中的查找路径如下图所示,第一次查找首先在当前节点的26个子空间中找下标对应"l"的节点是否存在(ASCII码与"a"相减得到下标),不存在直接返回查找失败;若存在,则将Node设置为"l",依次迭代,在查找完leet四个字符后,Node停留在了"t",此时需要判断是否为终止节点,否则为另一个单词的前缀。

每个键在 trie 中表示为从根到内部节点或叶的路径。我们用第一个键字符从根开始。检查当前节点中与键字符对应的链接。有两种情况:

存在链接。我们移动到该链接后面路径中的下一个节点,并继续搜索下一个键字符。
不存在链接。若已无键字符,且当前结点标记为 isEnd,则返回 true。否则有两种可能,均返回 false :
还有键字符剩余,但无法跟随 Trie 树的键路径,找不到键。
没有键字符剩余,但当前结点没有标记为 isEnd。也就是说,待查找键只是Trie树中另一个键的前缀。

题目:字典树(前缀树)的实现:

字典树又称为前缀树或Trie树,是处理字符串常见的数据结构。假设组成所有单词的字符串是“a”~“z”,请实现字典树结构,并包含以下四个主要功能。

void insert(String word): 添加word,可以重复添加。

void delete(String word):删除word,如果word添加过多次,仅删除一个。

boolean search(String word):查询word是否在字典树中。

int prefixNumber(String pre):返回以字符串pre为前缀的单词数量。

leetcode 208 问题描述:

实现一个 Trie (前缀树),包含 insertsearch, 和 startsWith 这三个操作。

示例:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app"); 
trie.search("app"); // 返回 true

说明:

  • 你可以假设所有的输入都是由小写字母 a-z 构成的。
  • 保证所有输入均为非空字符串。

实现

这是一个前缀树的节点,path用于记录通过该节点的路径有多少条,insert时增加1条,delete时减少1条;end用于记录当前节点是否为终止节点,不为0即为终止节点。TrieNode数组用于索引下一个节点的元素,因为题中指定了输入单词为小写字母a-z,故创建长度为26的数组。

 1 class TrieNode {
 2     int path;
 3     int end;
 4     TrieNode[] map;
 5 
 6     TrieNode(){
 7         path = 0;
 8         end = 0;
 9         //针对字母a-z,所以初始化分配长度为26的空间
10         map = new TrieNode[26];
11     }
12 }

下面是Trie树操作实现,基本流程都是先判断输入单词是否为空,然后将其转化为char数组,依次遍历char数组,根据与'a'的ASCII码差值,得到该字符的下标,然后将该节点赋值给当前节点,递归地去寻找下一个节点。

public class Trie {
    private TrieNode root;
    public Trie(){
        root = new TrieNode();
    }

    public void insert(String word){
        if(word==null){
            return;
        }
        char[] chs = word.toCharArray();
        TrieNode node = root;
        node.path++;
        int index = 0;
        for (int i=0;i<chs.length;i++){
            index = chs[i]-'a';
            if(node.map[index]==null){
                node.map[index] = new TrieNode();
            }
            node = node.map[index];
            node.path++;
        }
        //表示该字符串的最后一个节点为重点,用于search处的判断
        node.end++;
    }

    //删除word,如果word添加过多次,仅删除一个。
    public void delete(String word){
        if(search(word)){
            char[] chs = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            node.path--;
            for(int i=0;i<chs.length;i++){
                index = chs[i]-'a';
                node.map[index].path--;
                node = node.map[index];
            }
            node.end--;
        }
    }

    public boolean search(String word){
        if(word == null){
            return false;
        }
        char[] chs = word.toCharArray();
        TrieNode node = root;
        int index = 0;
        for(int i=0;i<chs.length;i++){
            index = chs[i]-'a';
            if(node.map[index]==null){
                return false;
            }else{
                node = node.map[index];
            }
        }
//        if(node.end!=0){
//            return true;
//        }else {
//            return false;
//        }
        return node.end!=0;
    }

    /**返回以字符串pre为前缀的单词数量*/
    public int prefixNumber(String pre){
        if(pre==null){
            return 0;
        }

        char[] chs = pre.toCharArray();
        TrieNode node = root;
        int index = 0;
        for(int i=0;i<chs.length;i++){
            index = chs[i]-'a';
            if(node.map[index]==null){
                return 0;
            }
            node = node.map[index];
        }
        return node.path;
    }

    /** 判断树中是否存在以此字符串为前缀的单词 */
    public boolean startsWith(String prefix) {
        if(prefix==null){
            return false;
        }
        char[] chs = prefix.toCharArray();
        TrieNode node =root;
        int index = 0;
        for(int i=0;i<chs.length;i++){
            index = chs[i]-'a';
            if(node.map[index]==null){
                return false;
            }
            node = node.map[index];
        }
//    return true; 
return node.path!=0; } }

参考:

[1] https://www.cnblogs.com/hengzhezou/p/11046886.html 字典树(Trie树)的实现

[2] https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/ leetcode 208 官方题解

猜你喜欢

转载自www.cnblogs.com/y4oung/p/11888865.html