trie樹-單詞樹-實現敏感詞屏蔽和詞頻統計


前幾天都看一個敏感詞屏蔽算法的文章,寫的挺好,順着思路寫了下去,實現了一下,算法效率還是槓槓的。。。

一、單詞樹介紹

利用的是單詞樹的算法,先看看什麼叫單詞樹。單詞樹也叫trie 樹也稱爲字典樹。最大的特點就是共享字符串的公共前綴來達到節省空間的目的。

例如,字符串 "abc"和"abd"構成的單詞樹如下:

在這裏插入圖片描述

樹的根節點不存任何數據,每整個個分支代表一個完整的字符串。像 abc 和 abd 有公共前綴 ab,所以我們可以共享節點 ab。如果再插入 abf,則變成這樣:

在這裏插入圖片描述

這樣看來能實現的功能就很顯而易見了,例如詞頻統計,單詞查找,還有就是遊戲裏的敏感詞屏蔽。

二、實現思路

來具體說說實現的思路吧。

2.1 詞頻統計和單詞查找

這兩個都是同一種思路。即下面代碼裏的find_word_exists函數,詞頻統計加個累計就好了。

關鍵在創建單詞樹的時候,需要添加子節點,另外還要標記單詞是否在此處是完整單詞。然後將一個個字符插入即可。

2.2 敏感詞屏蔽

這個稍微複雜點。即下面代碼裏的sensitive_word_filter函數。

需要三個指針來遍歷實現,兩個在檢查的單詞上,一個在單詞樹上。

1、首先指針 p1 指向 root,指針 p2 和 p3 指向字符串第一個字符

在這裏插入圖片描述

2、然後從字符串的 a 開始,檢測有沒有以 a 作爲前綴的敏感詞,直接判斷 p1 的孩子節點中是否有 a 這個節點就可以了,顯然這裏沒有。接着把指針 p2 和 p3 向右移動一格。

在這裏插入圖片描述

3、然後從字符串 b 開始查找,看看是否有以 b 作爲前綴的字符串,p1 的孩子節點中有 b,這時,我們把 p1 指向節點 b,p2 向右移動一格,不過,p3不動。

在這裏插入圖片描述

4、判斷 p1 的孩子節點中是否存在 p2 指向的字符c,顯然有。我們把 p1 指向節點 c,p2 向右移動一格,p3不動。

在這裏插入圖片描述

5、判斷 p1 的孩子節點中是否存在 p2 指向的字符d,這裏沒有。這意味着,不存在以字符b作爲前綴的敏感詞。這時我們把p2和p3都移向字符c,p1 還是還原到最開始指向 root。

在這裏插入圖片描述

6、和前面的步驟一樣,判斷有沒以 c 作爲前綴的字符串,顯然這裏沒有,所以把 p2 和 p3 移到字符 d。

在這裏插入圖片描述

到這裏應該差不多懂了。。。後面都一樣。那開始動手實踐。

三、代碼實現

這裏的詞頻統計,單詞查找和敏感詞屏蔽都實現了,如下;

#include <iostream>
#include <stdio.h>
using namespace std;

#pragma pack(1)
struct trie_node
{
    static const int letter_count = 26;
    int count;  // 字符的次數
    bool is_terminal; // 完整單詞的標誌
    char letter; // 當前節點的字符
    trie_node* childs[letter_count]; // 子節點

    trie_node(): letter(0), count(1), is_terminal(false)
    {
        for(int i = 0; i < letter_count; ++i)
        {
            childs[i] = NULL;
        }
    }
};
#pragma pack()

class trie
{
private:
    trie_node* _root_node;
public:
    trie(): _root_node(NULL)
    {
    }
    ~trie()
    {
        delete_trie(_root_node);
    }

    trie_node* create()
    {
        trie_node* node = new trie_node();
        return node;
    }

    void insert(const char* str)
    {
        if(NULL == _root_node || NULL == str)
        {
            _root_node = create();
        }
        trie_node* next_node = _root_node;

        while(*str != 0)
        {
            int index = *str - 'a';
            if(NULL == next_node->childs[index])
            {
                next_node->childs[index] = create();
            }
            else
            {
                next_node->childs[index]->count++;
            }
            next_node = next_node->childs[index];
            next_node->letter = *str;
            str++;
        }

        next_node->is_terminal = true;
    }

    bool find_word_exists(const char* str)
    {
        if(NULL == _root_node || NULL == str)
        {
            printf("condition is null\n");
            return false;
        }

        trie_node* cur_node = _root_node;

        do
        {
            cur_node = cur_node->childs[*str - 'a'];
            if(NULL == cur_node)
            {
                return false;
            }
            str++;
        }while (*str != 0);

        return cur_node->is_terminal; /* 直接看當前是否有完整單詞的標誌 */
    }

    void sensitive_word_filter(char* str)
    {
        if(NULL == _root_node || NULL == str)
        {
            printf("condition is null\n");
            return ;
        }

        char* pre = str;
        char* cur = str;
        trie_node* cur_node = _root_node;

        do
        {
            int index = *cur - 'a';
            if(NULL != cur_node->childs[index])
            {
                if(cur_node->childs[index]->is_terminal == true) /* 找到敏感詞 */
                {
                    while(pre != cur) /* 替換敏感詞 */
                    {
                        *pre = '*';
                        pre++;
                    }
                    *pre = '*';

                    // 向後移動,重新開始單詞樹查找
                    cur++;
                    pre = cur;
                    cur_node = _root_node;
                    continue;
                }
                cur_node = cur_node->childs[index];
                cur++;
            }
            else
            {
                /* 單詞樹需要重新開始查找。檢測的文本向後移動一步(前面的指針)然後查找 */
                pre++;
                cur = pre;
                cur_node = _root_node;
            }
        }while (*cur != 0);

        return;
    }

    void delete_trie(trie_node* node)
    {
        if(NULL == node)
        {
            return ;
        }
        for (int i = 0; i < trie_node::letter_count; i++)
        {
            if(NULL != node->childs[i])
            {
                delete_trie(node->childs[i]);
            }
        }
        delete node;
    }
};



int main(int argc, char** argv)
{
    if(argc < 2)
    {
        printf("Usage: ./a.out word\n");
        return -1;
    }

    char* word = NULL;
    if(NULL != argv[1])
    {
        word = argv[1];
    }
    else
    {
        return -2;
    }

    trie trie_tree = trie();
    trie_tree.insert("apps");
    trie_tree.insert("apply");
    trie_tree.insert("append");
    trie_tree.insert("back");
    trie_tree.insert("backen");
    trie_tree.insert("basic");

    /*1. 詞頻統計,和單詞查找*/
    bool is_find = trie_tree.find_word_exists(word);
    if(is_find)
    {
        printf("find word\n");

    }
    else
    {
        printf("not find\n");
    }

    /*2. 敏感詞屏蔽*/
    trie_tree.sensitive_word_filter(word);
    printf("word = %s\n", word);

    return 0;
}

./a.out apps
運行結果:

find word
word = ****

./a.out backhahaha
運行結果:

not find
word = ****hahaha

原理參考鏈接:https://blog.csdn.net/m0_37907797/article/details/103272967

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