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

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