数据结构 ---[实现字典树[前缀树](Trie)]

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 ---[实现字典树[前缀树](Trie)]相关的知识,希望对你有一定的参考价值。

Trie(前缀树或者字典树),主要针对于字符串的检索问题;值不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串

现在给你一本英语单词字典,要求快速查到单词zoo,你要是一个一个地往过找,是比较耗时的;
而如果从单词的前缀开始找,先找到z,然后接着找zo,再接着找,就找到zoo了;

简易做个字典树的图看看;

在这棵树中,可找到单词app,apple,all,allow,big,book,dept

1.字典树添加单词

在具体实现的过程中,这个结点要有一个标记值,确认此处是否为一个单词的末尾;
在其中还要多个指向下一个存储了字符的结点;

初步完成

package com.company.segmenttree;

import java.util.HashMap;
import java.util.Map;

/**
 * @author by CSDN@小智RE0
 * @date 2021-11-28 16:08
 */
public class Trie 
    //存储的节点;
    private class Node 
        //判断是否为单词
        private boolean isWord;
        //存储指向该节点下的所有字符对应的节点;
        private Map<Character, Node> next;

        private Node() 
            this.isWord = false;
            this.next = new HashMap<>();
        
    

    //根节点;
    private Node root;
    //单词的个数;
    private int size;

    public Trie() 
        this.root = new Node();
        this.size = 0;
    

    /**
     * 在节点树添加新单词
     *
     * @param newStr 新的字符;
     */
    public void buildStr(String newStr) 
        if (newStr == null || newStr.length() == 0) 
            return;
        
        //进行添加前,新建下一个节点;
        Map<Character, Node> child = root.next;
        //对字符串进行循环,依次存入;
        for (int i = 0; i < newStr.length(); i++) 
            char c = newStr.charAt(i);
            //首先判断是否为第一次存入,为该map的根节点下挂接;
            if (!child.containsKey(c)) 
                Node node = new Node();
                //判断是否为最后一位字符,为该字符节点标记单词标志位 isWord;
                if (i == newStr.length() - 1) 
                    node.isWord = true;
                    this.size += 1;
                

                child.put(c, node);
            


            //向后递进;
            child = child.get(c).next;
        
    

测试使用

//测试;
public static void main(String[] args) 
    String[] strArr ="app","apple","all","allow","book","big","dept";
    Trie trie = new Trie();
    //将字符串存入字典树;
    Arrays.stream(strArr).forEach(trie::buildStr);
    System.out.println("当前字典树的单词数->"+trie.getWordSize());

结果,和存入的数量一致;

当前字典树的单词数->7

但是,若先将字符"apple"存入,再添加"app"时就会判断到单词已存在,无法添加单词"app";
"allow"和"all"也是一样的;

那么就需要对之前的添加方法进行改进;

添加方法改进

不直接操作根点,每次添加单词时都使用临时节点进行操作;’

/**
 * 在节点树添加新单词
 * @param newStr 新的字符;
 */
public void buildStr(String newStr) 
    if (newStr == null || newStr.length() == 0) 
        return;
    
    //不直接操作移动根结点;而是使用一个临时节点进行操作;
    Node curNode = root;
    //对字符串进行循环,依次存入;
    for (int i = 0; i < newStr.length(); i++) 
        char c = newStr.charAt(i);
        Map<Character, Node> child = curNode.next;
        //首先判断是否为第一次存入,为该map的根节点下挂接;
        if (!child.containsKey(c)) 
            child.put(c, new Node());
        
        //向后递进;
        curNode = child.get(c);
    
    //对末尾字符进行标记;
    if(!curNode.isWord)
        curNode.isWord = true;
        this.size++;
    

测试

public static void main(String[] args) 
      String[] strArr ="apple","app","allow","all","book","big","dept","big","app","apple","all";
      Trie trie = new Trie();
      //将字符串存入字典树;
      Arrays.stream(strArr).forEach(trie::buildStr);
      System.out.println("当前字典树的单词数->"+trie.getWordSize());
  

即使有重复单词,也不会统计次数

当前字典树的单词数->7

2.判断是否存在指定单词,判断是否存在指定前缀的单词;

/**
* 查询指定的单词是否存在
* @param destStr 指定单词
*/
public boolean searchStr(String destStr)
   if(destStr==null||destStr.length()==0)
       return false;
   
   //同样用临时操作节点;
   Node curNode = root;
   for (int i = 0; i < destStr.length(); i++) 
       char c = destStr.charAt(i);
       Map<Character,Node> child = curNode.next;;
       //若不存在就结束;
       if(!child.containsKey(c))
          return false;
       
       //向后递进;
       curNode =child.get(c);
   
   //最终还需判断走过的节点是否为一个单词;
   return curNode.isWord;


/**
* 判断是否有指定前缀的单词存在;
* @param destPre 指定前缀
*/
public boolean searchPreStr(String destPre)
   if(destPre==null||destPre.length()==0)
       return false;
   
   //同样用临时操作节点;
   Node curNode = root;
   for (int i = 0; i < destPre.length(); i++) 
       char c = destPre.charAt(i);
       Map<Character,Node> child = curNode.next;;
       //若不存在就结束;
       if(!child.containsKey(c))
           return false;
       
       //向后递进;
       curNode =child.get(c);
   
   return true;

测试使用

//测试;
public static void main(String[] args) 
    String[] strArr ="apple","app","allow","all","book","big","dept","big","app","apple","all";
    Trie trie = new Trie();
    //将字符串存入字典树;
    Arrays.stream(strArr).forEach(trie::buildStr);
    System.out.println("当前字典树的单词数->"+trie.getWordSize());
    System.out.println("单词 ap 是否存在"+trie.searchStr("ap"));
    System.out.println("以前缀 ap 开头的单词是否存在"+trie.searchPreStr("ap"));

当前字典树的单词数->7
单词 ap 是否存在false
以前缀 ap 开头的单词是否存在true

3.判断是否存在指定单词(这时会将单词中的.号视为任意字母)

/**
 * 判断单词是否存在; 会将.(点号) 视为任意字母;
 * @param desStr 指定的单词
 */
public boolean searchSpecial(String desStr)
    if(desStr == null|| desStr.length()==0)
        return false;
    
    //调用方法;从匹配字符串的第一个字符开始;
    return searchSpecial(root,desStr,0);


/**
 * 判断指定单词是否存在,注意单词中的.点号会表示任意字母;
 * @param node   当前操作节点
 * @param desStr 指定的单词
 * @param i      指定单词的索引
 */
private boolean searchSpecial(Node node, String desStr, int i) 
    //1.递归结束条件;
    if (desStr.length() == i) 
        return true;
    
    //递归操作;
    //用临时节点操作;
    Node curNode = node;
    char c = desStr.charAt(i);
    //若取到的不是.号;
    if (c != '.') 
        Map<Character, Node> child = curNode.next;
        if (!child.containsKey(c)) 
            return false;
        
        //向后递归判断;
        return searchSpecial(child.get(c), desStr, i + 1);
    
    //若为.号;
    else 
        Map<Character, Node> child = curNode.next;
        //取到所有的键;若有一个匹配就符合;
        Set<Character> characters = child.keySet();
        for (Character character : characters) 
            if (searchSpecial(child.get(character), desStr, i + 1)) 
                return true;
            
        
        return false;
    


/**
 * @author by CSDN@小智RE0
 * @date 2021-11-28 16:08
 */
public class Trie 
    //存储的节点;
    private class Node 
        //判断是否为单词
        private boolean isWord;
        //存储指向该节点下的所有字符对应的节点;
        private final Map<Character, Node> next;

        private Node() 
            this.isWord = false;
            this.next = new HashMap<>();
        
    

    //根节点;
    private final Node root;
    //单词的个数;
    private int size;

    public Trie() 
        this.root = new Node();
        this.size = 0;
    

    //获取单词数量;
    public int getWordSize() 
        return this.size;
    

    /**
     * 在节点树添加新单词
     *
     * @param newStr 新的字符;
     */
    public void buildStr(String newStr) 
        if (newStr == null || newStr.length() == 0) 
            return;
        
        //不直接操作移动根结点;而是使用一个临时节点进行操作;
        Node curNode = root;
        //对字符串进行循环,依次存入;
        for (int i = 0; i < newStr.length(); i++) 
            char c = newStr.charAt(i);
            Map<Character, Node> child = curNode.next;
            //首先判断是否为第一次存入,为该map的根节点下挂接;
            if (!child.containsKey(c)) 
                child.put(c, new Node());
            
            //向后递进;
            curNode = child.get(c);
        
        //对末尾字符进行标记;
        if (!curNode.isWord) 
            curNode.isWord = true;
            this.size++;
        
    

    /**
     * 查询指定的单词是否存在
     *
     * @param destStr 指定单词
     */
    public boolean searchStr(String destStr) 
        if (destStr == null || destStr.length() == 0) 
            return false;
        
        //同样用临时操作节点;
        Node curNode = root;
        for (int i = 0; i < destStr.length(); i++) 
            char c = destStr.charAt(i);
            Map<Character, Node> child = curNode.next;
            ;
            //若不存在就结束;
            if (!child.containsKey(c)) 
                return false;
            
            //向后递进;
            curNode = child.get(c);
        
        //最终还需判断走过的节点是否为一个单词;
        return curNode.isWord;
    

    /**
     * 判断是否有指定前缀的单词存在;
     *
     * @param destPre 指定前缀
     */
    public boolean searchPreStr(String destPre) 
        if (destPre == null || destPre.length() == 0) 
            return false;
        
        //同样用临时操作节点;
        Node curNode = root;
        for (int i = 0; i < destPre.length(); i++) 
            char c = destPre.charAt(i);
            Map<Character, Node> child = curNode.next;
            ;
            //若不存在就结束;
            if (!child.containsKey(c)) 
                return false;
            
            //向后递进;
            curNode = child.get(c);
        
        return true;
    

    /**
     * 判断单词是否存在; 会将.(点号) 视为任意字母;
     *
     * @param desStr 指定的单词
     */
    public boolean searchSpecial(String desStr) 
        if (desStr == null || desStr.length() == 0) 
            return false;
        
        //调用方法;从匹配字符串的第一个字符开始;
        return searchSpecial(root, desStr, 0);
    

    /**
     * 判断指定单词是否存在,注意单词中的.点号会表示任意字母;
     *
     * @param node   当前操作节点
     * @param desStr 指定的单词
     * @param i      指定单词的索引
     */
    private boolean searchSpecial(Node node, String desStr, int i) 
        //1.递归结束条件;
        if (desStr.length() == i以上是关于数据结构 ---[实现字典树[前缀树](Trie)]的主要内容,如果未能解决你的问题,请参考以下文章

字典树208. 实现 Trie (前缀树)

数据结构 ---[实现字典树[前缀树](Trie)]

[LeetCode] 208. Implement Trie (Prefix Tree) 实现字典树(前缀树)

leetcode 208. 实现 Trie (前缀树)/字典树

LeetCode208. 实现 Trie (前缀树)(相关话题:字典树,前缀树)

LeetCode208. 实现 Trie (前缀树)(相关话题:字典树,前缀树)