Trie树

参考:https://blog.csdn.net/johnny901114/article/details/80711441

1.什么是Trie?

Trie又称字典树,单词查找树。它的查找速度主要和字符串长度有关。

Trie字典树主要用于存储字符串,Trie 的每个 Node 保存一个字符。用链表来描述的话,就是一个字符串就是一个链表。每个Node都保存了它的所有子节点。

例如我们往字典树中插入see、pain、paint三个单词,Trie字典树如下所示:

2.特点:

1>共享字符串的公共前缀来节省空间。

2>根节点root不存数据,每一整个分支代表一个字符串。

3>每个分支的内部可能也含有完整的字符串,所以我们可以对于那些是某个字符串结尾的节点做一个标记(isWord=true/false)。

3.实现:

  1 /**
  2  * 
  3  */
  4 package trie;
  5 
  6 import java.util.HashMap;
  7 import java.util.Map;
  8 
  9 /**
 10  * @author Administrator
 11  * 
 12  */
 13 public class Trie {
 14     private Node root;
 15     private int size;//单词的个数
 16     
 17     private class Node{
 18         public boolean isWord;
 19         public Map<Character,Node> next;
 20         
 21         public Node(){
 22             next=new HashMap<>();
 23         }
 24         
 25         public Node(boolean isWord){
 26             this();
 27             this.isWord=isWord;
 28         }
 29     }
 30     
 31     public Trie(){
 32         root = new Node();
 33     }
 34     public int getSize(){//获取
 35         return size;
 36     }
 37     public boolean isEmpty(){
 38         return size==0;
 39     }
 40     /**
 41      * 往Trie添加一个单词
 42      * @param word 单词
 43      */
 44     public void add(String word){
 45         Node current=root;
 46         char[] charArray = word.toCharArray();
 47         for (char c : charArray) {
 48             Node next = current.next.get(c);
 49             if(next==null){
 50                 current.next.put(c, next);
 51             }
 52             current=next;
 53         }
 54         if(!current.isWord){//如果当前node已经是一个word,则跳过
 55             size++;
 56             current.isWord=true;
 57         }
 58     }
 59     /**
 60      * 判断Trie中是否包含单词
 61      * @param word
 62      * @return true/false
 63      */
 64     public boolean contains(String word){
 65         Node current = root;
 66         char[] charArray = word.toCharArray();
 67         for (char c : charArray) {
 68             Node next = current.next.get(c);
 69             if(next==null){
 70                 return false;
 71             }
 72             current=next;
 73         }
 74         return current.isWord;
 75     }
 76     /**
 77      * 判断Trie中是否包含前缀
 78      * @param prefix
 79      * @return true/false
 80      */
 81     public boolean containsPrefix(String prefix){
 82         Node current = root;
 83         char[] charArray = prefix.toCharArray();
 84         for (char c : charArray) {
 85             Node next = current.next.get(c);
 86             if(next==null){
 87                 return false;
 88             }
 89             current=next;
 90         }
 91         return true;
 92     }
 93     /**
 94      * 删除一个单词
 95      * 1:如果单词的所有字母的都没有多个分支,删除整个单词
 96      * 2:如果单词是另一个单词的前缀,只需要把该word的最后一个节点的isWord的改成false
 97      * 3:如果单词的除了最后一个字母,其他的字母有多个分支
 98      * @param word
 99      * @return true/false
100      */
101     public boolean remove(String word){
102         Node current = root;
103         Node multiChildNode = null;
104         int multiChildNodeIndex = -1;
105         char[] charArray = word.toCharArray();
106         for (int i=0; i<charArray.length; i++) {
107             Node child = current.next.get(charArray[i]);
108             //如果Trie中不存在该单词
109             if(child==null){
110                 return false;
111             }
112             //如果当前节点的子节点个数大于1个
113             if(child.next.size() >1 ){
114                 multiChildNode=child;
115                 multiChildNodeIndex=i;
116             }
117             current=child;
118         }
119         //情况1:单词的所有字母都没有多余分支(分支都只有1个),则删除整个单词
120         if(multiChildNodeIndex == -1){
121             root.next.remove(charArray[0]);
122             size--;
123             return true;
124         }
125         //情况2:如果单词后面还有子节点
126         if(current.next.size() > 0){
127             //如果当前节点的isWord为true
128             if(current.isWord){
129                 current.isWord=false;
130                 size--;
131                 return true;
132             }
133             //否则,不存在该单词,该单词只是前缀
134             return false;
135         }
136         //情况3:如果单词的字母除了最后一个没有分支,其他字母存在分支
137         if(multiChildNodeIndex != charArray.length-1){//multiChildNodeIndex == -1 已经做过判断,这里 multiChildNodeIndex!=-1
138             multiChildNode.next .remove(charArray[multiChildNodeIndex+1]);
139             size--;
140             return true;
141         }
142         return false;
143     }
144 }

4.应用:

1>Trie 最大的特点就是利用了字符串的公共前缀,像我们有时候在百度、谷歌输入某个关键字的时候,它会给我们列举出很多相关的信息

 

这种就是通过Trie实现的。

2>Trie 树来实现敏感词过滤

5.时间复杂度:

如果敏感词的长度为 m,则每个敏感词的查找时间复杂度是 O(m),字符串的长度为 n,我们需要遍历 n 遍,所以敏感词查找这个过程的时间复杂度是 O(n * m)。

如果有 t 个敏感词的话,构建 trie 树的时间复杂度是 O(t * m)。

posted @ 2019-05-09 17:15  littlecarzz  阅读(241)  评论(0编辑  收藏  举报