最困難的事情就是認識自己!
個人博客,歡迎訪問!
前言:
Trie樹也稱爲字典樹、單詞查找樹,最大的特點就是共享字符串的公共前綴來達到節省空間的目的了。
然後可以根據它的公共前綴的特性來實現敏感詞過濾、自動聯想等功能。
抽象出trie樹的數據結構:
1、首先來看下trie樹的結構圖:
從上圖可以歸納出Trie樹的基本性質:
①根節點不包含字符,除根節點外的每一個子節點都包含一個字符。
②從根節點到某一個節點,路徑上經過的字符連接起來,爲該節點對應的字符串。
③每個節點的所有子節點包含的字符互不相同。
④從第一字符開始有連續重複的字符只佔用一個節點,比如上面的to,和ten,中重複的單詞t只佔用了一個節點
從上面歸納出的基本性質可以抽象出節點的class屬性:
1、是否爲葉子節點的標誌位 isWord ;
2、既能存儲此節點的值也能存儲其所有的子節點的 children 數據結構HashMap:
節點 (Node )的class類代碼:
/**
* @Title: Node
* @Description: trie樹的節點
*/
private class Node {
// 節點是否爲葉子節點的標誌;true:葉子節點,false:非葉子節點(用於子節點的節點)
public boolean isWord;
// 當前節點擁有的孩子節點,使用hashmap進行存儲,在查找子節點時的時間複雜度爲O(1)
public HashMap<Character, Node> children;
public Node(boolean isWord) {
this.isWord = isWord;
this.children = new HashMap<>();
}
public Node() {
this(false);
}
}
代碼奉上:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* Trie 樹實現
*/
public class Trie {
/**
* @Title: Node
* @Description: trie樹的節點類
*/
private class Node {
// 節點是否爲葉子節點的標誌;true:葉子節點,false:非葉子節點(用於子節點的節點)
public boolean isWord;
// 當前節點擁有的孩子節點,使用hashmap進行存儲,在查找子節點時的時間複雜度爲O(1)
public HashMap<Character, Node> children;
public Node(boolean isWord) {
this.isWord = isWord;
this.children = new HashMap<>();
}
public Node() {
this(false);
}
}
// trie樹的根節點
private Node root;
// trie樹中擁有多少分枝(多少個敏感詞)
private int size;
public Trie() {
this.root = new Node();
this.size = 0;
}
/**
* @Description: 返回trie樹中分枝樹(敏感詞樹)
*/
public int getSize() {
return size;
}
/**
* @Description: 向trie樹中添加分枝/敏感詞
* @param word 添加的敏感詞
*/
public void addBranchesInTrie(String word) {
// 設置當前節點爲根節點
Node cur = root;
char[] words = word.toCharArray();
for (char c : words) {
// 判斷當前節點的子節點中是否存在字符c
if (!cur.children.containsKey(c)) {
// 如果不存在則將其添加進行子節點中
cur.children.put(c, new Node());
}
// 當前節點進行變換,變換爲新插入到節點 c
cur = cur.children.get(c);
}
// 分枝添加完成後,將分枝中的最後一個節點設置爲葉子節點
if (!cur.isWord) {
cur.isWord = true;
// 分枝數(敏感詞數)加1
size++;
}
}
/**
* @Description: 判斷trie樹中是否存在某分枝/敏感詞
* @param word 敏感詞
* @return
*/
public boolean contains(String word) {
Node cur = root;
char[] words = word.toCharArray();
for (char c : words) {
if (!cur.children.containsKey(c)) {
return false;
}
cur = cur.children.get(c);
}
// 如果存在並且遍歷到trie樹中某個分支最後一個節點了,那此節點就是葉子節點,直接返回true
return cur.isWord;
}
/**
* @Description: 如果一段話中有trie樹中存儲的敏感詞則需將其進行替換爲 **; 例如:尼瑪的,替換爲 **的
* @param word 一段話,如果有敏感詞需要被替換的詞
* @return
*/
public String sensitiveWordReplace(String word) {
System.out.println("敏感詞替換前:" + word);
Node cur = root;
char[] words = word.toCharArray();
// 需要被替換的敏感詞
StringBuilder oldTemp = new StringBuilder();
// 需要替換成的星號
StringBuilder starTemp = new StringBuilder();
for (char c : words) {
if (!cur.children.containsKey(c)) {
// 如果當前節點的孩子節點中沒有此單詞則直接跳過此循環,進入下次循環
continue;
}
if (!cur.isWord) {
// 拼接上word和trie樹都有的字符
oldTemp.append(c);
starTemp.append("*");
cur = cur.children.get(c);
}
if (cur.isWord) {
// 進行敏感詞替換
word = word.replaceAll(oldTemp.toString(), starTemp.toString());
// 清空StringBuilder中內容
oldTemp.delete(0, oldTemp.length());
starTemp.delete(0, starTemp.length());
// 查找一個敏感詞並替換後,需要重新從根節點進行遍歷,所以當前節點指向root
cur = root;
}
}
System.out.println("敏感詞替換後:" + word);
return word;
}
// 存放trie樹中查詢到的聯想詞
private List<String> list = new ArrayList<String>();
/**
* @Description: 利用trie的公共前綴特性,可以實現關鍵詞自動聯想
* @param word
*/
public void prefixMatching(String word, Node root) {
Node cur = root;
char[] words = word.toCharArray();
StringBuilder str = new StringBuilder();
str.append(word);
for (int i = 0; i < words.length; i++) {
if (!cur.children.containsKey(words[i])) {
System.out.println("無關聯詞!");
return;
}
cur = cur.children.get(words[i]);
}
dfs(str, cur);
System.out.println("[ " + word + " ]在trie樹中的聯想詞:" + Arrays.toString(list.toArray()));
}
/**
* @Description: 節點遍歷
* @param word 需要查找的詞
* @param root 開始遍歷的根節點
*/
public void dfs(StringBuilder word, Node root) {
Node cur = root;
if (cur.isWord) {
list.add(word.toString());
if (cur.children.size() == 0) {
return;
}
}
for (Character s : cur.children.keySet()) {
word.append(s);
// 遞歸調用
dfs(word, cur.children.get(s));
word.delete(word.length() - 1, word.length());
}
}
// test
public static void main(String[] args) {
Trie t = new Trie();
// 插入敏感詞
t.addBranchesInTrie("麻痹");
t.addBranchesInTrie("尼瑪的");
t.addBranchesInTrie("狗日的");
t.addBranchesInTrie("nm的");
// 插入聯想詞
t.addBranchesInTrie("聯想雲科技");
t.addBranchesInTrie("聯盟");
t.addBranchesInTrie("聯和利泰擴招了");
System.out.println("trie樹中分枝的個數:" + t.size);
String word = "尼瑪的";
System.out.println("Trie樹中是否存在[ " + word + " ]敏感詞: " + t.contains(word));
// 敏感詞替換測試
t.sensitiveWordReplace("袞,尼瑪的傻子,你麻痹的,你各狗日的,早晚揍死你,nm的狗東西。");
// trie樹實現聯想測試
t.prefixMatching("聯", t.root);
}
}
代碼運行輸出:
❤不要忘記留下你學習的足跡 [點贊 + 收藏 + 評論]嘿嘿ヾ
一切看文章不點贊都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!非常感謝! ̄ω ̄=