Java 前缀树

前言
今天在做算法题的时候,遇到了一道用到前缀树这种数据结构的题目,借此机会,写篇博文,记录一下前缀树到底是什么?

一、前缀树介绍
  前缀树(字典树)是N叉树的一种特殊形式。通常来说,一个前缀树是用来存储字符串的。前缀树的每一个节点代表一个字符串(前缀)。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的原始字符串,以及通往该子节点路径上所有的字符组成的。

例如,现在我要存储字符串数组[“Y”,“YU”,“YUE”,“W”,“WU”,SHENG],那么前缀树的结构就如下图所示:
在这里插入图片描述

字典树有三个基本性质:

1、根节点不包含字符,除根节点外每一个节点都只包含一个字符
2、从根节点到某一个节点,路径上经过的字符连接起来,就是该节点对应的字符串(例如:结果U代表的字符串就是"YU")
3、每个节点的所有子节点包含的字符都不相同。

一、前缀树的表示
下面这个是N叉树节点的表示:

class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};

children表示这个节点的子节点,那么对于前缀树,如何表示字符和子节点之间的对应关系?我们下面主要介绍数组和Map两种方式,注意,上面这个代码跟下面就没什么太大关系了。

1、数组
假设我们存储的是只包含’a’-'z’字符的字符串,我们可以在每个节点中声明一个大小为26的数组来存储其子节点。对于特定字符 c,我们可以使用 c - ‘a’ 作为索引来查找数组中相应的子节点。同时,这个方法可能会导致空间的浪费。

class TrieNode {
    private static final int N = 26;
    private TrieNode[] children = new TrieNode[N];
    private boolean isEnd;//标识根节点到达该节点的路径是否是一个有效字符串
    
    //除了上面的定义,可能还需要定义一些诸如get,put操作。
};

2、Map
第二种方法是使用 Hashmap 来存储子节点。

我们可以在每个节点中声明一个Hashmap。Hashmap的键是字符,值是相对应的子节点。

class TrieNode {
    public Map<Character, TrieNode> children = new HashMap<>();
    private boolean isEnd;//标识根节点到达该节点的路径是否是一个有效字符串
    
    //除了上面的定义,可能还需要定义一些诸如get,put操作。
};

二、前缀树的插入(构造)
还是以上面的图片为例:
在这里插入图片描述

假如现在我们想往字符串数组[“Y”,“YU”,“YUE”,“W”,“WU”,SHENG]加入字符串”NB“和"SHI",那么步骤如下图所示:
在这里插入图片描述

1、插入字符串"NB"

(1)判断Root节点的子节点是否包含”NB“的前缀‘N’,没有就新建节点进行遍历赋值,有则往‘N’节点遍历。
在这里插入图片描述

(2)新建节点‘N’,继续往下遍历。
在这里插入图片描述

(3)遍历到要插入的字符串结束

在这里插入图片描述

1、插入字符串"SHI"

(1)
在这里插入图片描述

(2)
在这里插入图片描述

(3)
在这里插入图片描述

(4)
在这里插入图片描述

二、前缀树的查找
1、查找字符串
查找前缀树中,是否包含此字符串,步骤其实跟插入差不了多少,当遍历到为空时,直接返回false,遍历结束时,判断这个节点是否是一个有效字符即可。

2、查找字符串前缀
查找前缀树中的字符串,是否包含此前缀,同样,遍历为空时直接返回false,遍历结束不为空,则代表查找前缀树中某个字符串有这个前缀。

三、代码实现
1、数组实现
节点的代码实现:

class TrieNode {
    //子节点
    private TrieNode[] links;

    private final int R = 26;

    private boolean isEnd;

    public TrieNode() {
        links = new TrieNode[R];
    }

    //判断是否包含子节点
    public boolean containsKey(char ch) {
        return links[ch -'a'] != null;
    }
    //获取子节点
    public TrieNode get(char ch) {
        return links[ch -'a'];
    }
    //创建节点
    public void put(char ch, TrieNode node) {
        links[ch -'a'] = node;
    } 
    //设置成有效字符串
    public void setEnd() {
        isEnd = true;
    }
    //判断从根节点到这个节点路径表示的字符串是否有效
    public boolean isEnd() {
        return isEnd;
    }
}

前缀树代码实现:

class Trie {
    private TrieNode root;

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

    // 插入
    public void insert(String word) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            char currentChar = word.charAt(i);
            if (!node.containsKey(currentChar)) {
                node.put(currentChar, new TrieNode());
            }
            node = node.get(currentChar);
        }
        node.setEnd();
    }
    
    private TrieNode searchPrefix(String word) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            char curLetter = word.charAt(i);
            if (node.containsKey(curLetter)) {
                node = node.get(curLetter);
            } else {
                return null;
            }
        }
        return node;
    }

    // 字符串的查找
    public boolean search(String word) {
        TrieNode node = searchPrefix(word);
        return node != null && node.isEnd();
    }
    //前缀的查找
    public boolean startsWith(String prefix) {
        TrieNode node = searchPrefix(prefix);
        return node != null;
    }

    public static void main(String[] args) {
        String[] a = {"yue","wu","sheng"};
        Trie trie = new Trie();
        for(String s : a){
            trie.insert(s);
        }
        System.out.println(trie.search("yu"));
        System.out.println(trie.startsWith("shen"));
    }
}

2、HashMap实现

class TrieNode {
    HashMap<Character,TrieNode> children = new HashMap();
    private boolean isEnd;

    public boolean isEnd() {
        return isEnd;
    }

    public void setEnd(boolean end) {
        isEnd = end;
    }
class Trie {
    private TrieNode root;

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

    public void insert(String str){
        TrieNode cur = root;
        for(int i=0;i<str.length();++i){
            char c = str.charAt(i);
            if(!cur.children.containsKey(c)){
                cur.children.put(c,new TrieNode());
            }
            cur = cur.children.get(c);
        }
        cur.setEnd(true);
    }

    private TrieNode searchPrefix(String word) {
        TrieNode cur = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (cur.children.containsKey(c)) {
                cur = cur.children.get(c);
            } else {
                return null;
            }
        }
        return cur;
    }

    // 字符串的查找
    public boolean search(String word) {
        TrieNode node = searchPrefix(word);
        return node != null && node.isEnd();
    }
    //前缀的查找
    public boolean startsWith(String prefix) {
        TrieNode node = searchPrefix(prefix);
        return node != null;
    }



    public static void main(String[] args) {
        String[] a = {"yue","wu","sheng","wz","yu"};
        Trie trie = new Trie();
        for(String s : a){
            trie.insert(s);
        }
        System.out.println(trie.search("yut"));
        System.out.println(trie.startsWith("she"));
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值