字典树(Trie树)两种实现方式详解

目录

字典树介绍

字典树二维数组实现

实现思路

代码实现

字典树节点链表实现

实现思路

代码实现


字典树介绍

        字典树,又称单词查找树。

        其根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。

        如下图所示是apple、was、war、ward构成的字典树。

字典树二维数组实现

字典树的常用基本操作为:插入、查找,删除操作比较少见。

实现思路

  1. 用二维数组 trie[i][j]=k 存储父节点编号为 i、第j个子节点的节点的编号。但是如果单纯以 j 作为子节点的个数则需要一个数组来存储节点代表的字符,而且查找时也并不方便,因此这里在存储时 j=字符-‘a’+1 ,这样 j 的值最大时为26。查找字符c时只需查找 trie[i][j][父节点编号][字符c-‘a’+1]即可。
  2. 用数组flag[k]标识编号为k的节点是否到达单词的结束。如下图apple,‘e’节点就需要被标识。

 例如,以apple、was、war、ward构造字典树:

        按照输入顺序构造的逻辑字典树结构如左下图,而在二维数组的存储如右图所示(图中表格对应a到z,部分字母省略)。

//输入apple的构造流程:
1、trie[0]['a']=1; //'a'代表'a'字符所在位置即'a'-'a'+ 1
2、trie[1]['p']=2;
3、trie[2]['p']=3;
4、trie[3]['l']=4;
5、trie[4]['e']=5;
   flag[5]=true; //此编号为5的节点为结尾

 

 查找单词最小前缀:

         按上述方法构造好字典树后(假设构造的是前缀字典树,且树上单词均为完整前缀),需要查找单词最小前缀(以ward为例):

1、trie[0][‘w’] 存在,值为 6  //'w'代表'w'字符所在位置即'w'-'w'+ 1
2、trie[6][‘a’] 存在,值为 7
3、trie[7][‘r’ ]存在,值为 9,且flag[9]=true 即war为一个完整的单词前缀

 

代码实现


import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @ClassName TrieArray
 * @Description TODO
 * @Author SSRS
 * @Date 2022-07-07 23:26
 * @Version 1.0
 */
public class TrieArray {
    private int trie[][];
    private boolean flag[];//对应编号是否是单词或前缀的结束
    private int total;//当前总节点数

    public TrieArray() {
        trie = new int[100000][28];
        flag = new boolean[2800000];
        total = 0;
    }

    /**
     * @MethodName insert
     * @Description TODO
     * @Author SSRS
     * @Date 2022-07-07 23:36
     * @Param [word]
     * @ReturnType void
     */
    public void insert(String word) {
        int parent = 0;
        boolean flag1 = false;
        for (char c : word.toCharArray()) {
            if (trie[parent][c - 'a' + 1]==0){
                trie[parent][c - 'a' + 1] = total + 1;
                total++;
                flag1=false;
            }else {
                flag1=true;
            }
            parent = trie[parent][c - 'a' + 1];

        }
        if (flag1){
            flag[parent] =true;
        }else {
            flag[total] = true;
        }

    }

    /**
     *@MethodName search
     *@Description TODO 查找单词word的最小前缀,如果没有找到返回其本身
     *@Author SSRS
     *@Date 2022-07-07 23:43
     *@Param [word]
     *@ReturnType java.lang.String
     */
    public String search(String word) {
        int parent = 0;
        StringBuilder result = new StringBuilder();
        for (char c : word.toCharArray()) {
            result.append(c);
            if (trie[parent][c - 'a' + 1] == 0) {
                break;
            }
            if (flag[trie[parent][c - 'a' + 1]]) {
                return result.toString();
            }
            parent = trie[parent][c - 'a' + 1];
        }
        return word;
    }

    /**
     *@MethodName replaceWords
     *@Description TODO 648. 单词替换 https://leetcode.cn/problems/replace-words
     *@Author SSRS
     *@Date 2022-07-08 12:40
     *@Param [dictionary, sentence]
     *@ReturnType java.lang.String
     */
    public String replaceWords(List<String> dictionary, String sentence) {
        for (String root : dictionary) {
            insert(root);
        }
        String result = "";
        String[] words = sentence.split(" ");
        for (String word : words) {
            result+=search(word)+" ";
        }
        return result.trim();
    }

    public static void main(String[] args) {
        String[] dictionaryArr = new String[]{"ca","cat","bat","rat"};
        List<String> dictionary=Arrays.stream(dictionaryArr).collect(Collectors.toList());
        String sentence = "the cattle was rattled by the battery";
        System.out.println(new TrieArray().replaceWords(dictionary,sentence));

    }

}

字典树节点链表实现

实现思路

class Node{
    private Node[] next; //子节点
    private int flag; //是否单词结束
    public Node(int flag){
        next=new Node[26];
        this.flag=flag;
    }
}
  1. 构造Node节点树形结构,一个父节点对应多个子节点;为方便查找,【子节点对应字符-‘a’】 作为子节点位于父节点next[]的数组下标;查找字符c时只需查找 【父节点.next[c-'a']】即可。
  2. 用节点内flag标识该节点是否是单词的结束位置(如有多个相同前缀,flag则代表该相同前缀数目)。

代码实现

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 *@ClassName Trie
 *@Description TODO
 *@Author SSRS
 *@Date 2022-07-08 12:06
 *@Version 1.0
 */
public class Trie {
    class Node{
        private Node[] next; //子节点
        private int flag; //是否单词结束
        public Node(int flag){
            next=new Node[26];
            this.flag=flag;
        }
    }
    private int total; //节点数
    public Node root; //根节点
    public Trie(){
        total=0;
        root=new Node(0);
    }

    /**
     *@MethodName insert
     *@Description TODO 插入
     *@Author SSRS
     *@Date 2022-07-08 12:39
     *@Param [word]
     *@ReturnType void
     */
    public void insert(String word) {
        Node parent = root;
        boolean flag1 = false;
        for (int i=0;i<word.length();i++) {
            char c=word.charAt(i);
            if (Objects.isNull(parent.next[c - 'a'])){
                if (i==word.length()-1){
                    parent.next[c - 'a'] = new Node(1);
                }else {
                    parent.next[c - 'a'] = new Node(0);
                }
                total++;
            }else {
                if (i==word.length()-1){
                    parent.next[c - 'a'].flag++;
                }
            }
            parent = parent.next[c - 'a'];
        }
    }

    /**
     *@MethodName search
     *@Description TODO 查找单词word的最小前缀,如果没有找到返回其本身
     *@Author SSRS
     *@Date 2022-07-08 12:39
     *@Param [word]
     *@ReturnType java.lang.String
     */
    public String search(String word) {
        Node parent = root;
        StringBuilder result = new StringBuilder();
        for (char c : word.toCharArray()) {
            result.append(c);
            if (Objects.isNull(parent.next[c - 'a'])){
                break;
            }
            if (parent.next[c - 'a'].flag>0) {
                return result.toString();
            }
            parent = parent.next[c - 'a'];
        }
        return word;
    }

    /**
     *@MethodName replaceWords
     *@Description TODO 648. 单词替换 https://leetcode.cn/problems/replace-words
     *@Author SSRS
     *@Date 2022-07-08 12:40
     *@Param [dictionary, sentence]
     *@ReturnType java.lang.String
     */
    public String replaceWords(List<String> dictionary, String sentence) {
        for (String root1 : dictionary) {
            insert(root1);
        }
        String result = "";
        String[] words = sentence.split(" ");
        for (String word : words) {
            result+=search(word)+" ";
        }
        return result.trim();
    }

    public static void main(String[] args) {
        String[] dictionaryArr = new String[]{"catt","cat","bat","rat"};
        List<String> dictionary= Arrays.stream(dictionaryArr).collect(Collectors.toList());
        String sentence = "the cattle was rattled by the battery";
        System.out.println(new Trie().replaceWords(dictionary,sentence));

    }
}

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SS上善

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值