Java使用Trie樹算法實現敏感詞替過濾、根據關鍵詞自動聯想

最困難的事情就是認識自己!

個人博客,歡迎訪問!

前言:

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);
    }

}

代碼運行輸出:

 

不要忘記留下你學習的足跡 [點贊 + 收藏 + 評論]嘿嘿ヾ

一切看文章不點贊都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!非常感謝! ̄ω ̄=

參考資料:

1、【圖解算法面試】記一次面試:說說遊戲中的敏感詞過濾是如何實現的?

2、前綴樹(Trie)原理及Java實現

3、Trie樹(字典樹/前綴樹)Java實現

4、Trie 樹實現搜索引擎自動聯想

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章