Trie 字典树

本文详细介绍了Trie树的数据结构及其在字符串检索、词频统计等场景的应用,通过实例代码展示了如何使用Trie树进行前导匹配、统计前缀个数、统计完整单词个数及实现词频统计TopK的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Trie树 (特例结构树) 时间复杂度O(n),一般排序算法nlgn

Trie树,又称单词查找树、字典树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.优点:最大限度地减少无谓的字符串比较,查询效率比哈希表高.Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
缺点: Trie树的内存消耗非常大.当然,或许用左儿子右兄弟的方法建树的话,可能会好点
适用场景:字符串检索,词频统计,搜索引擎的热门查询
//场景一:前导匹配
//场景二:统计前缀个数
//场景三:统计整个完整次频个数
//场景四:词频统计topk (trie+小根堆)

publicclass Trie {

    private Vertexroot = new Vertex();

    protectedclass Vertex {

        protectedintwords;// 单词个数

        protectedintprefixes;// 前缀个数

        protectedcharc;// 前缀个数

        protected Vertex[]edges; // 子节点

        Vertex() {

            this.words = 0;

            this.prefixes = 0;

            edges =new Vertex[26];//26个字母按照索引0~25代表

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

                edges[i] =null;

            }

        }

    }

    /**

     * 获取tire树中所有的词

     *

     * @return

     */

    public List<String>listAllWords() {

        List<String> words = newArrayList<String>();

        Vertex[] edges = root.edges;

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

            if (edges[i] !=null) {

                String word = "" + (char) ('a' + i);//i索引强转为相应ascii

                depthFirstSearchWords(words,edges[i], word);

            }

        }

        return words;

    }

    privatevoid depthFirstSearchWords(List words, Vertexvertex,

            String wordSegment) {

        if (vertex.words != 0) {

            words.add(wordSegment);

        }

        Vertex[] edges = vertex.edges;

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

            if (edges[i] !=null) {

                String newWord = wordSegment +(char) ('a' + i);

                depthFirstSearchWords(words,edges[i], newWord);

            }

        }

    }

 

    /**

     * 计算指定前缀单词的个数

     *

     * @param prefix

     * @return

     */

    publicint countPrefixes(String prefix) {

        return countPrefixes(root, prefix);

    }

 

    privateint countPrefixes(Vertex vertex, StringprefixSegment) {

        if (prefixSegment.length() == 0) {// reach the last character of the word

            return vertex.prefixes;

        }

        char c = prefixSegment.charAt(0);

        int index = c -'a';

        if (vertex.edges[index] ==null) {// theword does NOT exist

            return 0;

        } else {

            return countPrefixes(vertex.edges[index],prefixSegment.substring(1));

        }

    }

 

    /**

     * 计算完全匹配单词的个数

     *

     * @param word

     * @return

     */

    publicint countWords(String word) {

        return countWords(root, word);

    }

    privateint countWords(Vertex vertex, StringwordSegment) {

        if (wordSegment.length() == 0) {// reach the last character of the word

            return vertex.words;

        }

        char c = wordSegment.charAt(0);

        int index = c -'a';

        if (vertex.edges[index] ==null) {// theword does NOT exist

            return 0;

        } else {

            return countWords(vertex.edges[index],wordSegment.substring(1));

        }

    }

    publicvoid addWord(String word) {

        addWord(root, word);

    }

    privatevoid addWord(Vertex vertex, String word) {

        if (word.length() == 0) {// if all characters of the word has been added

            vertex.words++;//到最后一个节点完整的词频个数

        } else {

            vertex.prefixes++;//每次前缀词频个数

            char c = word.charAt(0);

            c = Character.toLowerCase(c);

        vertex.c=c;

            int index = c -'a';

            if (vertex.edges[index] ==null) {// if theedge does NOT exist

                vertex.edges[index] =new Vertex();

            }

 

            addWord(vertex.edges[index],word.substring(1));// go the the next

                                                               // character

        }

    }

 

    /**

     * 返回指定字段前缀匹配最长的单词。

     *

     * @param word

     * @return

     */

    public String getMaxMatchWord(Stringword) {

        String s = "";

        String temp = "";// 记录最近一次匹配最长的单词

        char[] w = word.toCharArray();

        Vertex vertex = root;

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

            char c = w[i];

            c = Character.toLowerCase(c);

            int index = c -'a';

            if (vertex.edges[index] ==null) {//如果没有子节点

                if (vertex.words != 0)//如果是一个单词,则返回

                    return s;

                else

                    //如果不是一个单词则返回null

                    returnnull;

            } else {

                if (vertex.words != 0)

                    temp = s;

                s += c;

                vertex = vertex.edges[index];

            }

        }

        // trie中存在比指定单词更长(包含指定词)的单词

        if (vertex.words == 0)//

            return temp;

        return s;

    }

   

    //递归调用,先根遍历

    publicstaticvoid preOrder(Vertex vertex){

        if(vertex==null){return;}

        System.out.print(vertex.c); //先打印根

        Vertex[] edges = vertex.edges;

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

        preOrder(edges[i]);

        }

    }

 

    publicstaticvoid main(String args[])// Just used for test

    {

        Trie trie = new Trie();

        trie.addWord("ba");

        trie.addWord("cde");

        trie.addWord("a");

        trie.addWord("abcf");

        trie.addWord("abcd");

        trie.addWord("abcd");

        trie.addWord("abcd");

        trie.addWord("abcd");

        trie.addWord("ba");

        trie.addWord("abcedfddd");

 

        String maxMatch = trie.getMaxMatchWord("abcedfddd");//场景一:前导匹配

        int count = trie.countPrefixes("abc");//场景二:统计前缀个数

        int count1 = trie.countWords("abcd");//场景三:统计整个完整次频个数

        trie.preOrder(trie.root);//场景四:词频统计topk (trie+小根堆)

        System.out.println(maxMatch);

        List<String> list =trie.listAllWords();

        Iterator listiterator =list.listIterator();

        while (listiterator.hasNext()) {

        String s = (String)listiterator.next();

        System.out.print(s+" ");

        }

        System.out.println("");

        System.out.println("prefixes:" + count);

        System.out.println("countWords:" + count1);

    }

}

### 使用Trie字典树进行敏感词检测的实现方法 #### 背景介绍 Trie字典树是一种高效的树形据结构,广泛应用于字符串处理领域。它通过共享公共前缀的方式减少了存储空间和查询时间,在敏感词检测场景下表现尤为突出[^1]。 #### 据结构设计 Trie树的核心在于其节点的设计。每个节点通常包含以下几个部分: - **字符**:当前节点所代表的字符。 - **子节点集合**:指向下一个可能的字符节点。 - **结束标志位 (isEnd)**:用于标记某个路径是否构成了完整的敏感词[^2]。 以下是Trie节点的一个简单定义: ```java class TrieNode { private boolean isEnd; private Map<Character, TrieNode> children; public TrieNode() { this.children = new HashMap<>(); this.isEnd = false; } public boolean isEnd() { return isEnd; } public void setEnd(boolean end) { isEnd = end; } public Map<Character, TrieNode> getChildren() { return children; } } ``` #### 插入敏感词 为了构建一棵能够完成敏感词检测的Trie树,首先需要将所有的敏感词插入到树中。每插入一个新词时,按照字母顺序逐层创建节点并更新`isEnd`属性以标记完整词语的位置[^3]。 示例代码如下所示: ```java public class Trie { private final TrieNode root; public Trie() { root = new TrieNode(); } // 向Trie树中添加一个新的敏感词 public void addWord(String word) { TrieNode current = root; for (char c : word.toCharArray()) { if (!current.getChildren().containsKey(c)) { current.getChildren().put(c, new TrieNode()); } current = current.getChildren().get(c); } current.setEnd(true); // 设置最后一个节点为终止状态 } } ``` #### 进行敏感词过滤 当完成了所有敏感词的初始化之后,就可以基于这棵Trie树来进行实际的文字审查工作了。具体做法是从输入文本的第一位开始逐步向下匹配直到找到整个句子或者遇到未记录下来的分支为止[^2]。 下面给出了一段Java程序片段展示如何替换掉原文中的不当表述部分: ```java public String filter(String text) { StringBuilder sb = new StringBuilder(); int length = text.length(); for(int i=0;i<length;){ int j=i; TrieNode node=root; while(j<length && node!=null){ char ch=text.charAt(j); node=node.getChildren().get(ch); if(node==null || !node.isEnd()){ break; } if(node.isEnd()){ sb.append("***"); i=j+1; continue; } j++; } if(i>=j){ sb.append(text.charAt(i)); i++; }else{ i=j; } } return sb.toString(); } ``` 此函会逐一扫描给定字符串里的每一项成分,并依据预先建立好的规则决定保留原样还是替换成指定符号序列(这里选用的是三个星号)。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值