字典树的实际应用场景之敏感词过滤(PHP 和 Java)

3 篇文章 0 订阅
1 篇文章 0 订阅

字典树的实际应用场景之敏感词过滤(PHP 和 Java)

Trie(字典树,前缀树)的介绍及特性

介绍

Trie又称单词查找树,是一种树形结构,是哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
优点:非常适合操作字符串,利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
缺点:虽然不同单词共享前缀,但其实trie是一个以空间换时间的算法,每个结点只存储一个字符浪费了

Trie树的一些特性:

1.根节点不包含字符,除根节点外每一个节点都只包含一个字符,每个结点拥有26个子结点j
2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
3.每个节点的所有子节点包含的字符都不相同
4.如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间
5.插入查找的复杂度为O(n),n为字符串长度。


## 敏感词过滤

在很多场景都会用到敏感词过滤,比如在网站提交的内容,游戏中的聊天等等…那么这些敏感词是如何被过滤掉的呢?其实这就是一个字符串的匹配过程,我们很容易就可以想到的就是准备一个敏感词库,然后用每一个敏感词去要过滤的文本中匹配,匹配成功则用 *** 来代替敏感词,不同的匹配算法的时间复杂度是不一样的,比如敏感词的长度为m,待过滤文本的长度为 n,有如下几种匹配算法:

  • 暴力破解法,利用两层循环来实现,时间复杂度为O(m * n);
  • KMP算法:时间复杂度为 O(n + m);
  • 字典树:时间复杂度为O(n);

Trie 实现敏感词的过滤

首先,在进行敏感词过滤之前我们需要有一个敏感词库,用这个敏感词库来建立字典树,比如我们现在有两个敏感词:“de”, “bca”,建立字典树,根节点不存放任何东西,其他每个子节点存放一个字符,深红色代表单词结尾,如下:
在这里插入图片描述

建立完字典树之后,我们就要利用这棵由敏感词组成的字典树匹配字符串了。

现在,我们有一段文本 “abcadef" ,目的是将其中的 “de”, “bca”,过滤掉,具体算法如下:

  1. 为了遍历字符串和字典树,我们假设有三个指针,p1,p2,p3,其中p1指向字典树根节点,p2 和 p3 指向字符串的第一个字符,如下:
    在这里插入图片描述

  2. 然后从字符串的 a 开始,检测有没有以 a 作为前缀的敏感词,直接判断 p1 的孩子节点中是否有 a 这个节点就可以了,显然这里没有。接着把指针 p2 和 p3 向右移动一格。
    在这里插入图片描述

  3. 然后从字符串 b 开始查找,看看是否有以 b 作为前缀的字符串,p1 的孩子节点中有 b,这时,我们把 p1 指向节点 b,由于此时 b 不是单词的结尾,所以p3 向右移动一格,不过,p2 不动。
    在这里插入图片描述

  4. 判断 p1 的孩子节点中是否存在 p3 指向的字符 c,显然有。我们把 p1 指向节点 c,p3 向右移动一格,p2 不动。
    在这里插入图片描述

  5. 判断 p1 的孩子节点中是否存在 p3 指向的字符 a,显然有,且 a 是字符串 “bca” 的结尾。这意味着,p2 到 p3之间为敏感词 “bca”,把 p2 和 p3 指向的区间那些字符替换成 *。这时我们把 p2 和 p3 都移向字符 d,p1 还是还原到最开始指向 root。
    在这里插入图片描述

  6. 和前面的步骤一样,判断有没以 d 作为前缀的字符串,显然这里有 “de”,所以把 p2 和 p3 移到字符 f。
    在这里插入图片描述

  7. 因为根节点没有子节点 f,所以匹配结束。

在 Java 中可以利用 HashMap 来存放一层树结点,则每个敏感词的查找时间复杂度是 O (1),字符串的长度为 n,我们需要遍历 1 遍,所以敏感词查找这个过程的时间复杂度是 O (n * 1)。如果每个敏感词的平均长度为 m,有 t 个敏感词的话,构建 trie 树的时间复杂度是 O (t * m)。

但在实际的应用中,构建 trie 树的时间复杂度可以忽略,因为 trie 树我们可以在一开始就构建了,以后可以无数次重复利用的了。


Java示例

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.CharUtils;

public class MyTrie {
	private class TrieNode {
        /**
         * 是否敏感词的结尾
         */
        private boolean isEnd = false;

        /**
         * 下一层节点
         */
        Map<Character, TrieNode> subNodes = new HashMap<>();

        /**
         * 添加下一个敏感词节点
         *
         * @param c
         * @param node
         */
        public void addSubNode(Character c, TrieNode node) {
            subNodes.put(c, node);
        }

        /**
         * 获取下一个敏感词节点
         *
         * @param c
         * @return
         */
        public TrieNode getSubNode(Character c) {
            return subNodes.get(c);
        }

        public void setEnd(boolean end) {
            this.isEnd = end;
        }

        public boolean getIsEnd() {
            return this.isEnd;
        }


    }

    private static final String DEFAULT_REPLACEMENT = "***";

    /**
     * 根节点
     */
    private TrieNode rootNode;


    public MyTrie() {
        rootNode = new TrieNode();
    }

    /**
     * 识别特殊符号
     */
    private boolean isSymbol(Character c) {
        int ic = (int) c;
        // 0x2E80-0x9FFF 东亚文字范围
        return !CharUtils.isAsciiAlphanumeric(c) && (ic < 0x2E80 || ic > 0x9FFF);
    }


    /**
     * 将敏感词添加到字典树中
     * @param text
     */
    public void addWord(String text) {
        if (text == null || text.trim().equals(""))
            return;

        TrieNode curNode = rootNode;
        int length = text.length();
        //遍历每个字
        for (int index = 0; index < length; ++index) {
            Character c = text.charAt(index);
            /**
             * 过滤特殊字符
             */
            if (isSymbol(c))
                continue;

            TrieNode nextNode = curNode.getSubNode(c);
            // 第一次添加的节点
            if (nextNode == null) {
                nextNode = new TrieNode();
                curNode.addSubNode(c, nextNode);
            }
            curNode = nextNode;

            // 设置敏感词标识
            if (index == length - 1) {
                curNode.setEnd(true);
            }
        }
    }

    /**
     * 过滤敏感词
     * @param text
     * @return
     */
    public String filter(String text) {
        if (text == null || text.trim().equals(""))
            return text;

        String replacement = DEFAULT_REPLACEMENT;
        StringBuilder result = new StringBuilder();

        TrieNode curNode = rootNode;
        int begin = 0; // 回滚位置
        int position = 0;// 当前位置

        while (position < text.length()) {
            Character c = text.charAt(position);

            // 过滤空格等
            if (isSymbol(c)) {

                if (curNode == rootNode) {
                    result.append(c);
                    ++begin;
                }

                ++position;
                continue;
            }

            curNode = curNode.getSubNode(c);

            // 当前位置的匹配结束
            if (curNode == null) {
                // 以begin开始的字符串不存在敏感词
                result.append(text.charAt(begin));
                // 跳到下一个字符开始测试
                position = begin + 1;
                begin = position;
                // 回到树初始节点,重新匹配
                curNode = rootNode;

            } else if (curNode.getIsEnd()) {
                // 发现敏感词,从begin到position的位置用replacement替换掉
                result.append(replacement);
                position = position + 1;
                begin = position;
                // 回到树初始节点,重新匹配
                curNode = rootNode;
            } else {
                ++position;
            }

        }

        result.append(text.substring(begin));

        return result.toString();
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {

       MyTrie tree = new MyTrie();
        //添加敏感词
        tree.addWord("de");
        tree.addWord("bca");

        //过滤敏感词
        String res = tree.filter("ab cad ef");
        System.out.println("敏感词:de bca \n输入数据:ab cad ef \n过滤结果:"+res);
    }
}

Java示例运行结果
经过测试本示例代码对汉字过滤也同样有效(注:只做了部分测试,请勿直接使用在项目中
在这里插入图片描述

PHP示例


/**
 * 字典树-敏感词过滤
 */
class TreeMap
{
    public $data;  // 节点字符
    public $children = [];  // 存放子节点引用(因为有任意个子节点,所以靠数组来存储)
    public $isEndingChar = false;  // 是否是字符串结束字符
 
    public function __construct($data)
    {
        $this->data = $data;
    }
}
 
class TrieTree
{
    /**
     * 敏感词数组
     * 
     * @var array
     * @author qpf
     */
    public $trieTreeMap = array();
 
     public function __construct()
    {
        $this->trieTreeMap = new TreeMap('/');
    }
 
    /**
     * 获取敏感词Map
     * 
     * @return array
     * @author qpf
     */
    public function getTreeMap()
    {
        return $this->trieTreeMap;
    }
 
    /**
     * 添加敏感词
     * 
     * @param array $txtWords
     * @author qpf
     */
    public function addWords(array $wordsList)
    {
        foreach ($wordsList as $words) {
            $trieTreeMap = $this->trieTreeMap;
            $len = mb_strlen($words);
            for ($i = 0; $i < $len; $i++) {
                $word = mb_substr($words, $i, 1);
                if(!isset($trieTreeMap->children[$word])){
                    $newNode = new TreeMap($word);
                    $trieTreeMap->children[$word] = $newNode;
                }
                $trieTreeMap = $trieTreeMap->children[$word];
            }
            $trieTreeMap->isEndingChar = true;
        }
    }
 

    /**
     * 查找对应敏感词
     * 
     * @param string $txt
     * @return array
     * @author qpf
     */
    public function search($txt)
    {
    	if(empty($txt)) return [];
    	// $txt = str_replace(' ', '', $txt);
        $wordsList = array();
        $txtLength = mb_strlen($txt);
        for ($i = 0; $i < $txtLength; $i++) {
            $wordLength = $this->checkWord($txt, $i, $txtLength);
            if($wordLength > 0) {
                // echo $wordLength;
                $words = mb_substr($txt, $i, $wordLength);
                $wordsList[] = $words;
                $i += $wordLength - 1;
            }
        }
        return $wordsList;
    }
 
    /**
     * 敏感词检测
     * 
     * @param $txt
     * @param $beginIndex
     * @param $length
     * @return int
     */
    private function checkWord($txt, $beginIndex, $length)
    {
        $flag = false;
        $wordLength = 0;
        $trieTree = $this->trieTreeMap; //获取敏感词树
        for ($i = $beginIndex; $i < $length; $i++) {
            $word = mb_substr($txt, $i, 1); //检验单个字
            if (ctype_space($word)) {//检查是否有空格
            	$wordLength++; 
            	continue;
            }
            if (!isset($trieTree->children[$word])) { //如果树中不存在,结束        
                break;
            }
            //如果存在
            $wordLength++; 
            $trieTree = $trieTree->children[$word];
            if ($trieTree->isEndingChar === true) {  
                $flag = true;
                break;
            }
        }
        if($beginIndex > 0) {
            $flag || $wordLength = 0; //如果$flag == false  赋值$wordLenth为0
        }
        return $wordLength;
    }
    
}
 
$data = ['de', 'bca'];
$wordObj = new TrieTree();
$wordObj->addWords($data);
 
$txt = "ab cad ef";
$words = $wordObj->search($txt);

echo "<pre>-------getTreeMap----------";
print_r($wordObj->getTreeMap());
echo "<br>";

echo "<pre>----------words-----------";
print_r($words);


PHP 代码运行结果
经过测试本示例代码对汉字过滤也同样有效(注:只做了部分测试,请勿直接使用在项目中
在这里插入图片描述
在这里插入图片描述

参考文章:
字典树的实际应用场景之敏感词过滤(附 Java 实现代码)
【数据结构】Trie(字典树,前缀树)及其实现
基于PHP + TRIE树实现敏感词过滤算法

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用 Java 中的正则表达式来实现文本内容敏感词过滤。具体步骤如下: 1. 定义敏感词列表,将所有需要过滤敏感词保存到一个数组或列表中。 2. 构造正则表达式,将敏感词列表中的所有敏感词用竖线 "|" 连接起来,形成一个正则表达式。 3. 对文本内容进行过滤使用 String 类的 replaceAll() 方法,将文本中的敏感词替换成指定的字符或字符串。 下面是一个简单的示例代码: ```java import java.util.regex.Pattern; public class SensitiveWordFilter { private static final String[] sensitiveWords = {"敏感词1", "敏感词2", "敏感词3"}; private static final String REPLACEMENT = "***"; private static final String REGEX = String.join("|", sensitiveWords); private static final Pattern PATTERN = Pattern.compile(REGEX); public static String filter(String text) { return PATTERN.matcher(text).replaceAll(REPLACEMENT); } public static void main(String[] args) { String text = "这是一段包含敏感词的文本,敏感词1和敏感词2都出现了。"; String filteredText = SensitiveWordFilter.filter(text); System.out.println(filteredText); } } ``` 这个示例代码中,我们首先定义了敏感词列表 sensitiveWords,以及需要替换的字符串 REPLACEMENT。然后,我们将敏感词列表中的所有敏感词用竖线 "|" 连接起来,形成一个正则表达式 REGEX。最后,我们使用 Pattern 类将正则表达式编译成一个模式 PATTERN,然后在 filter() 方法中使用 PATTERN.matcher() 方法来匹配文本中的敏感词,并使用 replaceAll() 方法将敏感词替换成 REPLACEMENT。 在示例代码中,我们使用 main() 方法来演示如何使用 SensitiveWordFilter 类来过滤敏感词。在 main() 方法中,我们首先定义了一个包含敏感词的文本 text,然后调用 SensitiveWordFilter.filter() 方法对文本进行过滤,并将过滤后的结果打印出来。 ### 回答2: 在Java中实现文本内容的敏感词过滤可以通过以下步骤完成: 步骤1:构建敏感词字典 建立一个包含敏感词字典,可以将敏感词保存在一个List或Set等数据结构中,也可以将敏感词保存在一个文本文件中,读取到内存中进行使用。 步骤2:读取待过滤的文本 读取待过滤的文本内容,可以将文本保存在一个字符串变量中。 步骤3:敏感词过滤 遍历敏感词字典使用正则表达式等方式在文本中查找是否存在敏感词。可以使用Java提供的正则表达式类库,例如Java.util.regex类库,或者使用Apache Commons Lang等第三方类库。 步骤4:替换敏感词 通过替换或标记的方式将敏感词在文本中进行处理。可以使用String的replace方法将敏感词替换成指定的字符,例如“***”或“*”。也可以使用其他方式对敏感词进行处理,例如替换成全角空格等。 步骤5:返回过滤后的文本 返回过滤后的文本内容,可以直接输出结果或保存到文件等。 通过以上步骤,即可实现Java中文本内容的敏感词过滤。根据实际需要,可以进一步优化算法数据结构,提高过滤性能和效果。 ### 回答3: Java语言可以通过正则表达式和基本的字符串操作来实现文本内容的敏感词过滤。 首先,我们需要准备一个敏感词库,将敏感词以字符串数组的形式存储起来。 使用Java的字符串类提供的replace方法可以将文本中的敏感词替换为指定的字符串。我们可以遍历敏感词库中的敏感词,然后使用replace方法将文本中的敏感词替换为“***”等符号。 另一种方法是使用Java的正则表达式工具包,如Pattern类和Matcher类,将文本中的敏感词进行匹配。首先,将敏感词库中的敏感词使用竖线“|”连接起来,形成一个正则表达式,然后使用Pattern类进行编译。编译后的Pattern对象可以用于对文本进行匹配。当匹配到敏感词时,可以使用Matcher类的replace方法将敏感词替换为指定的字符串。 另外,为了提高敏感词过滤的效率,可以使用Trie树(字典树数据结构来存储敏感词库。Trie树可以将敏感词库构建成一个树状结构,使得在过滤文本时能够快速查找和匹配敏感词。 最后,我们可以将实现的文本内容敏感词过滤功能封装成一个方法,供其他程序调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值