算法数据结构-Trie树

数据结构与算法:前缀树(字典树、Trie树) 前缀树 阅读详情

  Trie树,又称为前缀树(因为某节点的后代存在共同的前缀,比如pan是panda的前缀),字典树,顾名思义它本事也是属于树这个数据结构体系中的一员,当然它也有很多变种,如后缀树,Radix Tree/Trie,PATRICIA tree,以及bitwise版本的crit-bit tree等等。我们通常使用Trie来处理字符串匹配的,使用它可以解决在一组字符串集合中快速查找某个字符串的问题。它为何能这么高效?它的查询的时间复杂度是多少?缺点是什么?

我们结合一个项目中的需求来讲解描述下Trie树的作用,并且结合例子来自我感受下它的效率。

需求:抖音视频平台,虎牙斗鱼在线直播平台,起点纵横小说平台。这些平台都会有用户评论的功能(抖音中的评论,虎牙和斗鱼的在线弹幕,起点纵横的帖子评论)。既然存在用户评论的概念,那么对于用户评论的语句就要进行检测,对于一些敏感词汇就要进行处理(有将敏感词汇变更为*显示,也有提示用户需要修改评论,否则不能进行发表)。不论做何种后续的操作,那么如何能快速检测这些敏感词是关键。例如当用户存在这么一句评论:“i want fuck you”,那么其中包含了敏感词汇“fuck”。那么我们如何能快速的呢。一般我们收集这些敏感词汇组成一个字典,然后将语句和敏感词典进行匹配发现敏感词汇。

我们就使用上述的Trie树来进行敏感词典(假设当前的敏感词典为fuck,bitch,bullshit,suck)的构建,这个词语在Trie树中是这么体现的呢,如下图所示:

从上图可以看出Trie树的一些特点:

  1. 根节点不包含任何字符,除了根节点任何一个节点只会包含一个字符
  2. 从根节点开始到某一节点中间所有经过的节点对应的字符连接起来就是该节点对应的字符串
  3. 一般使用不同颜色来区分节点类型,例如上述以黄色节点对应字符串是存储类型字符串用于后续对比,而非黄色节点对应为非存储类型字符串的或只是中间字符串
  4. 每个节点下的子节对应的字符都不相同

1.1:Trie树的插入非常的简单,将写入的单词的每个字符依次的写入到Trie树中,单词的每个字符都可以看作是树中的每一层。每个字母插入前先看字母对应的节点是否存在,存在则共享该节点,不存在则创建对应的节点。例如若我们将bitch这个单词写入到Tire树中(假设词典中已存在fuck,bullshit,suck)。第一步将bitch的首字母b插入到根节点后面,因为b节点已经存在,所以公用此节点;第二步写入i字母,在查看b节点的子发现只有u节点无i节点,所以创建i节点,链接到b节点之后;第三步重复第二步直到单词的尾节点,并标明此节点颜色为黄色(实际上这个颜色只是标志位的一种体现,我也可以使用其它颜色或者其它表现形式,标志位为了避免前缀词对词典的影响,例如pan是panda的前缀,那么若panda在Trie中存在,那么对应pan肯定也是在Trie中存在,而我们期盼的是panda属于词典而pan不属于,这个时候就是该标志位的作用时候了)。

1.2:那么Trie树的查询效率怎么样呢,Trie树的查询是和被检索的字符串的长度有关,例如我需要查询bullshit这个单词是否在词典中存在就会按照根路径向下检索匹配,直到找到尾节点为黄色并且包含的字母为t的时候,就代表bullshit在字典中是存在的,那么对应的时间复杂度为O(n)

1.3:Trie树的节点的删除较为复杂,通常分为2种情况:

  1. 若删除的字符串在Tire的存在是前缀词的存在,这种删除比较简单,只需要找到尾部节点将其节点的标志位转变下即可
  2. 若删除的字符串在Tire的存在是非前缀词的存在,这种删除较为复杂,需要从根节点开始向下遍历(按照字符串的路径向下遍历)删除,若当前节点下无其它节点(不在路径类的)是,可将该节点对应的分支全部移除,若存在则该节点属于公共节点不可被移除,直到尾部节点被移除。

2:Java代码对于Trie树基本实现:

2.1:基础代码实现


import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;

/**
 * java实现Trie树数据结构
 * 此树为不安全
 * @author fangyuan
 */
public class TrieTree implements Serializable, Iterable<String> {

    private static final long serialVersionUID = 8386482531322992189L;

    /**
     * 当前Trie树中包含元素个数
     */
    private int size;

    /**
     * 当前Trie树中包含节点数量
     */
    private long length;

    /**
     * 定义根节点
     */
    private TrieTreeNode root;

    /**
     * 用于存储当前树中元素
     */
    private List<String> eles;

    //定义节点类型
    /**
     * 标示该节点字符串应该在trie树中
     */
    static final int yellow = 1;

    /**
     * 普通节点
     */
    static final int white = 0;

    /**
     * 每一层树默认分支数
     */
    static final int minTreeLevel = 2;

    /**
     * 构建函数 初始化trie树
     */
    public TrieTree(){
        this.size=0;
        this.length=1;
        this.root = new TrieTreeNode('/',white);
        this.eles = new ArrayList<>(8);
    }



    /**
     * 定义Trie节点
     */
    class TrieTreeNode {

        //当前节点对应的字母
        private char ele;

        //定义该节点类型
        private int type;

        //当前节点所包含的字节点 后续使用其它结构对其进行优化
        private List<TrieTreeNode> nexts;

        public  TrieTreeNode(char ele,int type){
            this.ele = ele;
            this.type=type;
            this.nexts = new ArrayList<>(minTreeLevel);
        }

    }

    /**
     * 新增一个元素
     * @param str 被写入的元素字符串
     */
    public void addNewEle(String str){

        char ele;
        TrieTreeNode current = this.root;
        List<TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toCharArray();
        //将chars以节点的形式写入到Trie树中
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];
            //若词中包含空格直接去除
            if(ele ==' ') {
                continue;
            }
            //当前节点type
           int nodeType = i==chars.length-1?yellow:white;

            if(trieTreeNodes.size() == 0){
                //直接新建一个新节点
                TrieTreeNode trieTreeNode =  new TrieTreeNode(ele,nodeType);
                trieTreeNodes.add(trieTreeNode);
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                this.length++;
            }else{
                boolean flag = true;
                //遍历此层节点是否已包含了该字母
                for (int j=0;j<trieTreeNodes.size();j++) {
                    TrieTreeNode trieTreeNode  = trieTreeNodes.get(j);
                    if(trieTreeNode.ele == ele){
                        //说明当前节点已经存在 处理下前缀词
                        trieTreeNode.type =nodeType;
                        current = trieTreeNode;
                        trieTreeNodes = trieTreeNode.nexts;
                        flag = false;
                        break;
                    }
                }

                //若当前节点不包含则重新创建
                if(flag){
                    TrieTreeNode trieTreeNode =  new TrieTreeNode(ele,nodeType);
                    trieTreeNodes.add(trieTreeNode);
                    current = trieTreeNode;
                    trieTreeNodes = trieTreeNode.nexts;
                    this.length++;
                }
            }
        }
        //创建完成
        this.size++;

        eles.add(str);
    }

    /**
     * 删除一个元素
     * @param str 被删除的元素字符串
     */
    public void remove(String str){

        char ele;
        TrieTreeNode current = this.root;
        List<TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toCharArray();

        boolean flag = true;

        LinkedHashMap<List<TrieTreeNode>,TrieTreeNode> delQueue = new LinkedHashMap<>(chars.length);

        //遍历trie树
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];

            //若词中包含空格直接去除
            if (ele == ' ') {
                continue;
            }

            //遍历此层节点是否已包含了该字母
            for (int j=0;j<trieTreeNodes.size();j++) {
                TrieTreeNode trieTreeNode  = trieTreeNodes.get(j);
                if(trieTreeNode.ele == ele){
                    //若当前字符为该单词的最后一个字母 若该单词为一个前缀词只需要变更节点属性即可
                    if(i==chars.length-1 && trieTreeNode.type == yellow && trieTreeNode.nexts.size()>0){
                        //变更节点类型
                        trieTreeNode.type=white;
                        this.size--;
                        this.eles.remove(str);
                        return;
                    }
                    //记录当前节点到队列中 为后续删除做准备
                    delQueue.put(trieTreeNodes,trieTreeNode);
                    current = trieTreeNode;
                    trieTreeNodes = trieTreeNode.nexts;
                    break;
                }

                if(j==trieTreeNodes.size()-1){
                    //当前字符串在Trie中不存在 直接跳出
                    return;
                }
            }
        }

        //移除节点
        for(Map.Entry<List<TrieTreeNode>, TrieTreeNode> entry: delQueue.entrySet()){
            List<TrieTreeNode> k = entry.getKey();
            TrieTreeNode v = entry.getValue();
            if(v.nexts.size()<=1){
                if(flag) {
                    k.remove(v);
                    flag = false;
                }
                this.length--;
            }
        }
        //参数递减
        this.size--;
        this.eles.remove(str);
    }

    public int size(){
        return this.size;
    }

    /**
     * 查询的字符串是否在trie字典中存在
     * @param str 被查询的元素字符串
     * @return
     */
    public  boolean search(String str) {

        char ele;
        TrieTreeNode current = this.root;
        List<TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toCharArray();
        boolean flag = false;

        //遍历trie树
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];

            //若词中包含空格直接去除
            if(ele ==' ') {
                continue;
            }
            //遍历此层节点是否已包含了该字母
            for (int j=0;j<trieTreeNodes.size();j++) {
                TrieTreeNode trieTreeNode  = trieTreeNodes.get(j);
                if(trieTreeNode.ele == ele){
                    //若当前字符为该单词的最好一个字母 为避免前缀词的影响 需再次确认下 尾节点的类型
                    if(i==chars.length-1 && trieTreeNode.type == yellow){
                        return true;
                    }
                    current = trieTreeNode;
                    trieTreeNodes = trieTreeNode.nexts;
                    break;
                }

                if(j==trieTreeNodes.size()-1){
                    flag = true;
                }

            }
            if(flag){
                break;
            }
        }
        return false;
    }

    @Override
    public Iterator<String> iterator() {
        return this.eles.iterator();
    }

    /**
     * 实际调用list中接口处理
     * @param action
     */
    @Override
    public void forEach(Consumer<? super String> action) {
        this.eles.forEach(action);
    }

    public static void main(String[] args) {

        TrieTree trieTree = new TrieTree();

        String[] test = {"fuck","bitch","bullshit","bull","suck"};

        for(int i=0;i<test.length;i++){

            trieTree.addNewEle(test[i]);
        }

        //测试迭代功能
        trieTree.forEach(str->{
            System.out.println("-----初始元素集合------"+str);
        });

        //检测单词
        System.out.println("-------bitch------>"+trieTree.search("bitch"));

        System.out.println("------suck------->"+trieTree.search("suck"));

        System.out.println("------bull------->"+trieTree.search("bull"));


        trieTree.remove("bitch");

        trieTree.remove("suck");

        trieTree.remove("bull");

        //测试迭代功能
        trieTree.forEach(str->{
            System.out.println("-----被删除后的元素集合------"+str);
        });

        System.out.println("-------bitch------>"+trieTree.search("bitch"));

        System.out.println("------suck------->"+trieTree.search("suck"));

        System.out.println("------bull------->"+trieTree.search("bull"));

    }
}

最终测试的结果为:

2.2:优化版trie树

package com.fc.javacode.dataStructure.tree;

import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;

/**
 *
 *相对于TrieTree 优化子节点的寻址 使得整个Trie树的时间复杂度为O(n) n为树的深度
 * @author fangyuan
 */
public class TrieHashTree  implements Serializable, Iterable<String> {


    /**
     * 当前Trie树中包含元素个数
     */
    private int size;

    /**
     * 当前Trie树中包含节点数量
     */
    private long length;

    /**
     * 定义根节点
     */
    private TrieHashTree.TrieTreeNode root;

    /**
     * 用于存储当前树中元素
     */
    private List<String> eles;

    //定义节点类型
    /**
     * 标示该节点字符串应该在trie树中
     */
    static final int yellow = 1;

    /**
     * 普通节点
     */
    static final int white = 0;

    /**
     * 每一层树默认分支数
     */
    static final int minTreeLevel = 2;

    /**
     * 构建函数 初始化trie树
     */
    public TrieHashTree(){
        this.size=0;
        this.length=1;
        this.root = new TrieHashTree.TrieTreeNode('/',white);
        this.eles = new ArrayList<>(8);
    }


    /**
     * 定义Trie节点
     */
    class TrieTreeNode {

        //当前节点对应的字母
        private char ele;

        //定义该节点类型
        private int type;

        //当前节点所包含的字节点 后续使用其它结构对其进行优化
        private Map<Character,TrieTreeNode> nexts;

        public  TrieTreeNode(char ele,int type){
            this.ele = ele;
            this.type=type;
            this.nexts = new HashMap<>(minTreeLevel);
        }

    }

    /**
     * 新增一个元素
     * @param str 被写入的元素字符串
     */
    public void addNewEle(String str){

        char ele;
        TrieHashTree.TrieTreeNode current = this.root;
        Map<Character,TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toCharArray();
        //将chars以节点的形式写入到Trie树中
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];
            //若词中包含空格直接去除
            if(ele ==' ') {
                continue;
            }
            //当前节点type
            int nodeType = i==chars.length-1?yellow:white;

            //遍历此层节点是否已包含了该字母
            TrieTreeNode trieTreeNode ;
            if((trieTreeNode = trieTreeNodes.get(ele))!=null){
                //说明当前节点已经存在 处理下前缀词
                trieTreeNode.type =nodeType;
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                continue;
            }else{
                trieTreeNode =  new TrieHashTree.TrieTreeNode(ele,nodeType);
                trieTreeNodes.put(ele,trieTreeNode);
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                this.length++;
            }
        }
        //创建完成
        this.size++;
        eles.add(str);
    }



    /**
     * 查询的字符串是否在trie字典中存在
     * @param str 被查询的元素字符串
     * @return
     */
    public  boolean search(String str) {

        char ele;
        TrieHashTree.TrieTreeNode current = this.root;
        Map<Character,TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toCharArray();
        //将chars以节点的形式写入到Trie树中
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];
            //若词中包含空格直接去除
            if(ele ==' ') {
                continue;
            }

            //遍历此层节点是否已包含了该字母
            TrieTreeNode trieTreeNode ;
            if((trieTreeNode = trieTreeNodes.get(ele))!=null){
                //若当前字符为该单词的最好一个字母 为避免前缀词的影响 需再次确认下 尾节点的类型
                if(i==chars.length-1 && trieTreeNode.type == yellow){
                    return true;
                }
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                continue;
            }else{
                break;
            }

        }
        return false;
    }

    /**
     * 删除一个元素
     * @param str 被删除的元素字符串
     */
    public void remove(String str){

        char ele;
        TrieHashTree.TrieTreeNode current = this.root;
        Map<Character,TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toCharArray();

        LinkedHashMap<Map, Character> delQueue = new LinkedHashMap<>(chars.length);

        //遍历trie树
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];

            //若词中包含空格直接去除
            if (ele == ' ') {
                continue;
            }


            //遍历此层节点是否已包含了该字母
            TrieTreeNode trieTreeNode ;
            if((trieTreeNode = trieTreeNodes.get(ele))!=null){
                //若当前字符为该单词的最后一个字母 若该单词为一个前缀词只需要变更节点属性即可
                if(i==chars.length-1 && trieTreeNode.type == yellow && trieTreeNode.nexts.size()>0){
                    //变更节点类型
                    trieTreeNode.type=white;
                    this.size--;
                    this.eles.remove(str);
                    return;
                }
                //记录当前节点到队列中 为后续删除做准备
                delQueue.put(trieTreeNodes,ele);
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                continue;
            }else{
                return;
            }

        }

        boolean flag = true;

        //移除节点
        for(Map.Entry<Map, Character> entry: delQueue.entrySet()){
            Map k = entry.getKey();
            Character v = entry.getValue();
            if(k.size()<=1){
                if(flag) {
                    k.remove(v);
                    flag = false;
                }
                this.length--;
            }
        }
        //参数递减
        this.size--;
        this.eles.remove(str);
    }
    

    @Override
    public Iterator<String> iterator() {
        return this.eles.iterator();
    }

    /**
     * 实际调用list中接口处理
     * @param action
     */
    @Override
    public void forEach(Consumer<? super String> action) {
        this.eles.forEach(action);
    }


    public static void main(String[] args) {

        TrieHashTree trieTree = new TrieHashTree();

        String[] test = {"fuck","bitch","bullshit","bull","suck","婊子"};

        for(int i=0;i<test.length;i++){

            trieTree.addNewEle(test[i]);
        }

        //测试迭代功能
        trieTree.forEach(str->{
            System.out.println("-----初始元素集合------"+str);
        });

        //检测单词
        System.out.println("-------bitch------>"+trieTree.search("bitch"));

        System.out.println("------suck------->"+trieTree.search("suck"));

        System.out.println("------bull------->"+trieTree.search("bull"));


        trieTree.remove("bitch");

        trieTree.remove("suck");

        trieTree.remove("bull");

        //测试迭代功能
        trieTree.forEach(str->{
            System.out.println("-----被删除后的元素集合------"+str);
        });

        System.out.println("-------bitch------>"+trieTree.search("bitch"));

        System.out.println("------suck------->"+trieTree.search("suck"));

        System.out.println("------bull------->"+trieTree.search("bull"));

    }

}

最终结果:

上述代码中若该Trie树作用只是考虑英文单词的检测,其实更简便的方式,因为每个节点存储的子节点最多为26个,所以使用一个长度为26个字母的数组进行存储,并且每个存储的char字母-a就得到对应的在数组中对应的存储的位置了,这样也就避免了节点使用list结构需要进行的迭代,使用hashmap存储时存在hash冲突的情况了。但因为要考虑到汉字的因素,所以就不能只使用数组了。

3:实现最终的需求,用以检测一段文字中是否包含敏感词,若包含使用*号替换(此处要结合AC算法(Aho-Corasick)),也支持获取所有敏感词

package com.fc.javacode.dataStructure.tree;

import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;

/**
 * 使用AC算法结合Trie来做到高效的多文本匹配
 *
 */
public class TrieTreeAC implements Serializable, Iterable<String> {

    /**
     * 当前Trie树中包含元素个数
     */
    private int size;

    /**
     * 当前Trie树中包含节点数量
     */
    private long length;

    /**
     * 目前最大level深度 为了应对多层词结构(例如存储AB,ABC都是词典中一员需要将两者都处理)
     */
    private int maxLevel;

    /**
     * 定义根节点
     */
    private TrieTreeAC.TrieTreeNode root;

    /**
     * 用于存储当前树中元素
     */
    private List<String> eles;

    String xh = "";

    //定义节点类型
    /**
     * 标示该节点字符串应该在trie树中
     */
    static final int yellow = 1;

    /**
     * 普通节点
     */
    static final int white = 0;

    /**
     * 每一层树默认分支数
     */
    static final int minTreeLevel = 2;


    /**
     * 构建函数 初始化trie树
     */
    public TrieTreeAC(){
        this.size=0;
        this.length=1;
        this.root = new TrieTreeAC.TrieTreeNode('/',white,0);
        this.eles = new ArrayList<>(8);
    }


    /**
     * 定义Trie节点
     */
    class TrieTreeNode {

        //当前节点对应的字母
        private char ele;

        //定义该节点类型
        private int type;

        //当前节点所包含的字节点 后续使用其它结构对其进行优化
        private Map<Character, TrieTreeAC.TrieTreeNode> nexts;

        //当前节点对应level深度
        private int level;

        //当查询失败时跳到的节点
        private TrieTreeNode failTo;

        public  TrieTreeNode(char ele,int type,int level){
            this.ele = ele;
            this.type=type;
            this.level=level;
            this.nexts = new HashMap<>(minTreeLevel);
        }

    }

    /**
     * 记录字符串中
     */
    class Position{

        int startCursor;

        int endCursor;

        public  Position(int startCursor,int endCursor){
            this.startCursor = startCursor;
            this.endCursor = endCursor;
        }
    }

    /**
     * 初始化
     * @param entitys
     */
    public  void init(List<String> entitys){
        this.buildTrieTree(entitys);
        this.buildFailTo();
    }

    /**
     * 根据词典创建一个新的字典树
     */
    private void buildTrieTree(List<String> entitys){

        //将数据写入到Trie树
        entitys.forEach(str->{
            this.addNewEle(str);
        });

    }

    /**
     * 为每个节点构建匹配失败时的跳转-失败结点(即匹配失败时,跳转到哪个结点继续匹配)
     * 其思想为:第一层子节点的fail节点直接指定是根节点
     * 其它子节点的fail节点 是从其父节点指定的fail节点的子节点中寻找该节点中对应的字符,若存在则找到fail节点了,若不存在继续向上寻找,直到找相应的节点或最终的失败节点为root节点时结束 
     * 
     */
    private void buildFailTo() {
        //这里使用算法类似于BFS算法(核心是一样的,从根节点向外部一层一层处理 ,每一层都要为每个节点找到对应的fail节点)
        Queue<TrieTreeNode> queue = new LinkedList<TrieTreeNode>();

        queue.add(this.root);

        while(!queue.isEmpty()){

            TrieTreeNode  currentNode =  queue.poll();

            TrieTreeNode failTo = currentNode.failTo;

            //处理当前节点的子节点
            for (Map.Entry<Character, TrieTreeNode> entry :currentNode.nexts.entrySet()) {

                Character childEle = entry.getKey();
                TrieTreeNode childNode = entry.getValue();

                queue.add(childNode);
                //循环一直找到该节点的回退点
                while(true){
                    if(failTo == null){
                        //如果当前节点无回退点 直接以根节点作为回退点
                        childNode.failTo=this.root;
                        break;
                    }
                    //判断fail节点是其父节点的失败点中查找相应路径
                    if(failTo.nexts.containsKey(childEle)){
                        childNode.failTo = failTo.nexts.get(childEle);
                        break;
                    }else{
                        //继续在当前节点的失败节点中查找相应的路径
                        failTo = failTo.failTo;
                    }
                }
            }
        }

        //根路径fail指向root节点
        this.root.failTo = this.root;
    }

    /**
     * 将一段文字中敏感字符进行替换为*号格式
     * @param str 需要被替换的字符
     */
    public String replace(String str){
        List<Position> positions = getPositions(str);
        //获取对应位置上字符串
        StringBuilder sb = new StringBuilder(str);
        positions.forEach(position -> {
            sb.replace(position.startCursor,position.endCursor+1,this.xh.substring(0,position.endCursor-position.startCursor+1));
        });
        return sb.toString();
    }

    /**
     *
     * @param str
     * @return
     */
    public List<String> filter(String str){

        List<Position> positions = getPositions(str);

        List<String> rs = new ArrayList<>(positions.size());

        positions.forEach(position -> {
            rs.add(str.substring(position.startCursor,position.endCursor+1));
        });
        return rs;
    }

    /**
     *
     * @param str
     * @return
     */
    private List<Position> getPositions(String str){

        char ele;
        char[] chars = str.toLowerCase().toCharArray();
        //定义起点游标 遍历字符串起点游标
        int startCursor = 0;
        //定义end游标
        int endCurson = 0;

        List<Position> positions = new ArrayList<>(8);
        TrieTreeAC.TrieTreeNode current = this.root;

        //开始进行替换
        for(int i=0;i<chars.length;i++){

            endCurson = i;
            ele = chars[i];
            //遍历此层节点是否已包含了该字母
            TrieTreeAC.TrieTreeNode trieTreeNode ;

            if((trieTreeNode = current.nexts.get(ele))!=null){
                //处理AC中goto
                if( trieTreeNode.type == yellow ){
                    if(trieTreeNode.nexts.size()>0 &&trieTreeNode.level<this.maxLevel){
                        //虽然找到end节点 避免前缀词的影响继续向下迭代
                        //记录对应位置 继续向下寻找
                        Position position = new Position(startCursor,endCurson);
                        positions.add(position);
                        current = trieTreeNode;
                    }else{
                        //说明已找到词典 记录对应字典的位置
                        Position position = new Position(startCursor,endCurson);
                        positions.add(position);
                        //开始下一轮的词典的寻找
                        startCursor = i;
                        current = this.root;
                    }
                }else{
                    //继续下一个字符的匹配
                    current = trieTreeNode;
                    continue;
                }
            }else{
                //匹配失败 尝试进行跳转到失败节点进行下一次匹配
                //重置startCursor位置
                if(current.failTo.equals(this.root)){
                    //当前节点fialTo的节点为root根节点
                    //开始下一轮的词典的寻找

                    //重置位置
                    if(i <chars.length-1 && current.level>0){
                        startCursor = i;
                        i = i-1;
                    }else{
                        startCursor = i+1;
                    }
                }else{
                    //计算当前对应start位置 并更新
                    startCursor +=(i+1-startCursor - current.failTo.level);
                }
                current = current.failTo;
            }
        }
        return positions;
    }

    /**
     * 新增一个元素
     * @param str 被写入的元素字符串
     */
    public void addNewEle(String str){

        char ele;
        TrieTreeAC.TrieTreeNode current = this.root;
        Map<Character, TrieTreeAC.TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toLowerCase().toCharArray();
        //将chars以节点的形式写入到Trie树中
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];
            //若词中包含空格直接去除
            if(ele ==' ') {
                continue;
            }
            //当前节点type
            int nodeType = i==chars.length-1?yellow:white;
            //若当前类型为end节点 用以计算最大深度
            if(nodeType == yellow && maxLevel<i+1){
                //构建*库
                for(int n=0;n<(i+1)-maxLevel;n++){
                    this.xh+="*";
                }
                maxLevel = i+1 ;
            }
            //遍历此层节点是否已包含了该字母
            TrieTreeAC.TrieTreeNode trieTreeNode ;
            if((trieTreeNode = trieTreeNodes.get(ele))!=null){
                //说明当前节点已经存在 处理下前缀词
                trieTreeNode.type =nodeType;
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                continue;
            }else{
                trieTreeNode =  new TrieTreeAC.TrieTreeNode(ele,nodeType,i+1);
                trieTreeNodes.put(ele,trieTreeNode);
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                this.length++;
            }
        }
        //创建完成
        this.size++;
        eles.add(str);
    }


    /**
     * 查询的字符串是否在trie字典中存在
     * @param str 被查询的元素字符串
     * @return
     */
    public  boolean search(String str) {

        char ele;
        TrieTreeAC.TrieTreeNode current = this.root;
        Map<Character, TrieTreeAC.TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toLowerCase().toCharArray();
        //将chars以节点的形式写入到Trie树中
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];
            //若词中包含空格直接去除
            if(ele ==' ') {
                continue;
            }

            //遍历此层节点是否已包含了该字母
            TrieTreeAC.TrieTreeNode trieTreeNode ;
            if((trieTreeNode = trieTreeNodes.get(ele))!=null){
                //若当前字符为该单词的最好一个字母 为避免前缀词的影响 需再次确认下 尾节点的类型
                if(i==chars.length-1 && trieTreeNode.type == yellow){
                    return true;
                }
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                continue;
            }else{
                break;
            }
        }
        return false;
    }

    /**
     * 删除一个元素
     * @param str 被删除的元素字符串
     */
    public void remove(String str){

        char ele;
        TrieTreeAC.TrieTreeNode current = this.root;
        Map<Character, TrieTreeAC.TrieTreeNode> trieTreeNodes = this.root.nexts;
        //将当前词解析为char字符数组
        char[] chars = str.toLowerCase().toCharArray();

        LinkedHashMap<Map, Character> delQueue = new LinkedHashMap<>(chars.length);

        //遍历trie树
        for (int i = 0; i < chars.length; i++) {

            ele = chars[i];

            //若词中包含空格直接去除
            if (ele == ' ') {
                continue;
            }

            //遍历此层节点是否已包含了该字母
            TrieTreeAC.TrieTreeNode trieTreeNode ;
            if((trieTreeNode = trieTreeNodes.get(ele))!=null){
                //若当前字符为该单词的最后一个字母 若该单词为一个前缀词只需要变更节点属性即可
                if(i==chars.length-1 && trieTreeNode.type == yellow && trieTreeNode.nexts.size()>0){
                    //变更节点类型
                    trieTreeNode.type=white;
                    this.size--;
                    this.eles.remove(str);
                    return;
                }
                //记录当前节点到队列中 为后续删除做准备
                delQueue.put(trieTreeNodes,ele);
                current = trieTreeNode;
                trieTreeNodes = trieTreeNode.nexts;
                continue;
            }else{
                return;
            }
        }

        boolean flag = true;
        //移除节点
        for(Map.Entry<Map, Character> entry: delQueue.entrySet()){
            Map k = entry.getKey();
            Character v = entry.getValue();
            if(k.size()<=1){
                if(flag) {
                    k.remove(v);
                    flag = false;
                }
                this.length--;
            }
        }
        //参数递减
        this.size--;
        this.eles.remove(str);
    }

    @Override
    public Iterator<String> iterator() {
        return this.eles.iterator();
    }

    /**
     * 实际调用list中接口处理
     * @param action
     */
    @Override
    public void forEach(Consumer<? super String> action) {
        this.eles.forEach(action);
    }


    public static void main(String[] args) {

        TrieTreeAC trieTreeAC = new TrieTreeAC();

        List<String> test = new ArrayList<>();

        test.add("fuck");
        test.add("bitch");
        test.add("bullshit");
        test.add("bull");
        test.add("suck");
        test.add("婊子");
        test.add("我草");
        test.add("草");
        test.add("我草你");

        trieTreeAC.init(test);
        //
        String testStr = "你好你好啊,我bullshitbitc哈哈卧槽我草你呀啊呀呀发疯";
        System.out.println("=======原语句为=======>"+testStr);
        //测试替换功能
        String rs = trieTreeAC.replace(testStr);

        trieTreeAC.filter(testStr).forEach(str->{
            System.out.println(str);
        });

        System.out.println("=======处理过后的语句为=======>"+rs);


        //测试迭代功能
        trieTreeAC.forEach(str->{
            System.out.println("-----初始元素集合------"+str);
        });

        //检测单词
        System.out.println("-------bitch------>"+trieTreeAC.search("bitch"));

        System.out.println("------suck------->"+trieTreeAC.search("suck"));

        System.out.println("------bull------->"+trieTreeAC.search("bull"));


        trieTreeAC.remove("bitch");

        trieTreeAC.remove("suck");

        trieTreeAC.remove("bull");

        //测试迭代功能
        trieTreeAC.forEach(str->{
            System.out.println("-----被删除后的元素集合------"+str);
        });

        System.out.println("-------bitch------>"+trieTreeAC.search("bitch"));

        System.out.println("------suck------->"+trieTreeAC.search("suck"));

        System.out.println("------bull------->"+trieTreeAC.search("bull"));

    }

}

最终的结果满足要求:

源码地址

数据结构——22. KMP算法和Trie树 数组),当在某个节点匹配失败时,可以通过失败指针跳转到另一状态,从而实现在一个文本串中同时查找多个模式串,且时间复杂度为线性的。如果学有余力,可以了解一下这个概念,它会让你对 Trie 树和 KMP 的理解更加深刻。这个“跳转到哪里”的信息,就存储在一个预先计算好的辅助数组中,这个数组通常被称为。它在 Trie 树的结构上,为每个节点增加了一个“失败指针”(类似 KMP 的。从根节点开始,遍历单词的每个字符。KMP 算法是一种高效的字符串匹配算法,它的核心在于。从根节点开始,顺着单词的字符路径往下走。 阅读详情

相关推荐

看图轻松理解数据结构与算法系列(Trie树)

前言 推出一个新系列,《看图轻松理解数据结构和算法》,主要使用图片来描述常见的数据结构和算法,轻松阅读并理解掌握。本系列包括各种堆、各种队列、各种列表、各种树、各种图、各种排序等等几十篇的样子。 Trie树 Trie树,是一种搜索树,也称字典树或单词查找树,此外也称前缀树,因为某节点的后代存在共同的前缀。它的key都为字符串,能做到高效查询和插入,时间复杂度为O(k),k为字符串长度,缺点是如果大...

seaboat——a free boat on the sea.(公众号:远洋号) 900

敏感词过滤

敏感词过滤 主要内容: 前缀树 名称:Trie、字典树、查找树 特点:查找效率高,消耗内存大 应用:字符串检索、词频统计、字符串排序等 敏感词过滤器 定义前缀树 获取敏感词,根据敏感词,初始化前缀树 编写过滤敏感词的方法 实现功能: 传入字符串,过滤其中的敏感词字段,返回过滤后的字段和当前存在的敏感词集合 前缀树示例 1、假设有2个敏感词 ace bd,画出根节点,从敏感词中分析出第一层 2、继续分析后续字母,每一层对应敏感词第几个字母 3、在敏感词底层字母处做一个标记,遍历到中

weixin_46186045的博客 1662

JAVA:实现Trie树数据结构算法(附完整源码)

JAVA:实现Trie树数据结构算法(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 495

lambda表达式

lambda表达式 文 | 少鹏 (cherry) 1 初识lambda表达式 List&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;String&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; list = Arrays.asList(&amp;amp;amp;amp;amp;amp;amp;amp;quot;one&amp;amp;amp;amp;amp;amp;amp

cherry的博客 1191

C#与发那科机器人通信实战:从SDK配置到机械臂控制

本文详细介绍了如何使用C#与发那科机器人进行通信,从SDK配置到机械臂控制的完整实战流程。内容包括环境准备、通信连接建立、机械臂运动控制、异常处理与状态监控,以及高级功能实战,帮助开发者快速掌握工业机器人编程技术。

weixin_42524276的博客 458

华大HC32F460 ADC+DMA高效数据采集实战解析

本文深入解析了华大HC32F460微控制器中ADC与DMA协同工作的高效数据采集方案。通过实战代码演示,详细阐述了如何配置硬件触发、序列扫描及AOS事件联动,实现后台自动采集,从而解放CPU资源,提升系统实时性与可靠性,适用于传感器数据采集、音频处理等多种场景。

weixin_29189373的博客 177

Java 中常用的函数式接口Consumer

文章目录写在前面1、 Consumer 源码解析1.1、@FunctionalInterface注解1.2、void accept(T t)1.3、default Consumer andThen(Consumer<? super T> after)2、 Consumer 使用实例2.1、forEach(Consumer<? super T> action)2.2、格式化打印信息 实例 写在前面 java.util.function.Consumer 接口,一般用它来消费一个数据,

这里的分享,都是干货 3059

保姆级教程:用Docker Compose一键部署WVP-PRO+ZLM+录像服务(含Nginx反代)

本文提供了一套完整的容器化GB28181视频平台部署方案,通过Docker Compose实现WVP-PRO、ZLMediaKit(ZLM)和录像服务的一键式部署。详细介绍了环境准备、架构设计、容器化部署实战以及高级配置优化,帮助用户快速搭建高性能视频监控管理平台,适用于安防行业和物联网设备管理场景。

weixin_30415113的博客 861

【图解算法】Trie树

Trie树,字符串查找树

就爱摆烂 5160

【Claude】Extra inputs are not permitted 错误:代理剥离 Beta 标头的解决方案 bug报错已解决

深入分析Claude Code Extra inputs are not permitted 400错误的技术原理,解析代理剥离Beta标头的根因与多种修复方案。

一键难忘的博客 145

【数据同化第一期】基本概念介绍

【数据同化第一期】基本概念介绍

WW、forever的博客 1904

数据结构:Trie(前缀树/字典树)

Trie 又称字典树、前缀树和单词查找树,是一颗非典型的多叉树模型,即每个结点的分支数量可能为多个。Trie这个名字取自“retrieval”,检索,读音和 try 相同。

m0_63997099的博客 3950

ATE Pin Electronics电路学习总结

PinElectronics(PE)是ATE测试机的核心模拟前端电路,负责ATE与芯片间的信号转换与交互。主要包含四大功能模块:Driver实现逻辑信号到模拟电平的转换(VIH/VIL可编程);Receiver对DUT输出进行采样比较(VOH/VOL判定);ActiveLoad施加可编程电流负载;PPMU进行精密电压/电流测量。PE的性能直接影响功能测试和直流参数测试的精度,需定期校准维护。关键参数包括:Driver电压精度(±5mV)、Receiver比较精度(±10-50mV)、PPMU测量精度(±1-

歌颂苦难的行为是无耻的; 465

数据结构与算法学习笔记----Trie树

@ author: 明月清了个风。

weixin_60278491的博客 1208

【科研绘图系列】R语言绘制代谢物与临床表型相关性的森林图

本研究通过森林图展示了特定代谢物与三种临床比较之间的相关性分析结果。这种方法不仅直观地展示了每个代谢物的效应大小和不确定性,还允许我们比较不同代谢物在不同临床比较中的影响。森林图的结果表明,某些代谢物在特定临床比较中显示出显著的相关性,这可能为理解疾病的代谢机制提供了重要线索。

专注生信领域 565

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

算法基础_数据结构【KMP + Trie 树 + 并查集】

本篇博客是《算法基础_数据结构》的算法题型总结+模板习题练习。主要算法涉及:KMP、Trie树、并查集的算法。

序属秋秋秋的博客 1362

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

数据结构:kmp算法,Trie树,以及并查集的干货详解---小白也能看懂

kmp算法,Trie, 并查集的详解及其模板

2401_86619696的博客 1828

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

算法与数据结构-Trie树

Trie 树,也叫“字典树”。顾名思义,它是一个树形结构。它是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。当然,这样一个问题可以有多种解决方法,比如散列表、红黑树,或者我们前面几节讲到的一些字符串匹配算法,但是,Trie 树在这个问题的解决上,有它特有的优点。不仅如此,Trie 树能解决的问题也不限于此,我们一会儿慢慢分析。现在,我们先来看下,Trie 树到底长什么样子。我举个简单的例子来说明一下。

码农界的菜鸟 577

算法-数据结构-实现 Trie (前缀树)

算法-数据结构-实现 Trie (前缀树) 1 题目概述 1.1 题目出处 https://leetcode-cn.com/problems/implement-trie-prefix-tree/ 1.2 题目描述 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 示例: Trie trie = new Trie(); trie.insert(“apple”); trie.search(“apple”); // 返回 true trie.sea

迷路剑客个人博客 321

【数据结构与算法】深刨Trie树(字典树)

Trie树

qq_66314292的博客 807

【算法·数据结构】trie树(字典树)

有关trie树的概念 在进行文法分析的时候,通常需要检测一个单词是否在我们的单词列表里。为了提高查找和定位的速度,通常都要画出与单词列表所对应的单词查找树,其特点如下: 根节点不包含字母,除根节点外每一个节点都仅包含一个大写英文字母; 从根节点到某一节点,路径上经过的字母依次连起来所构成的字母序列,称为该节点对应的单词。单词列表中的每个词,都是该单词查找树某个节点所对应的单词; 在满足上述条件下,...

Ronaldo7_ZYB的博客 739

数据结构与算法10:递归树、Trie树、B+树

对于很多业务场景需要使用“二分思想”的情况下,基本上都可以拆解为二叉树这种数据结构来处理,比如归并排序、快速排序、二分查找、跳表等。归并排序的递归树是一棵满二叉树,在二分的过程中的时间复杂度是O(logn)。Trie树也叫“字典树”,它是一种专门处理字符串匹配的数据结构。B树是⼀种多叉平衡查找树,而且⾮叶⼦节点和叶⼦节点都会存储数据;B+树是只有叶⼦节点才会存储数据。

码农兴哥的技术笔记 1011

JAVA:实现Trie树数据结构算法(附带源码)

JAVA:实现Trie树数据结构算法(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 1067

数据结构与算法—Trie树

Trie,又经常叫前缀树,字典树等等。它有很多变种,如后缀树,Radix Tree/Trie,PATRICIA tree,以及bitwise版本的crit-bit tree。当然很多名字的意义其实有交叉。 Trie树是一种非常重要的数据结构,它在信息检索,字符串匹配等领域有广泛的应用,同时,它也是很多算法和复杂数据结构的基础,如后缀树,AC自动机等。 典型应用是用于统计和排序大量的字符串(但不...

weixin_30396699的博客 196

【数据结构算法】Trie树

数据结构-Trie树 Trie树是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 class Trie: # word_end = -1 def __init__(self): """ Initialize your data structure here.

weixin_43409812的博客 648
上一篇: Java实现LRU算法
下一篇: 算法数据结构-树(1)
F_Hello_World
博客等级 码龄11年 38粉丝 58原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值