BBS等文本内容网站,大都会有敏感词过滤功能,用来过滤掉用户输入的一些淫秽、反动、谩骂等内容。
实际上,这些功能最基本的原理就是字符串匹配算法,也就是通过维护一个敏感词的字典,当用户输入一段文字内容之后,通过字符串匹配算法,来查找用户输入的这段文字,是否包含敏感词。如果有,就用“***”把它替代掉。
单模式字符串匹配算法都可以处理这个问题。但是,对于访问量巨大的网站来说,比如淘宝,用户每天的评论数有几亿、甚至几十亿。这时候,对敏感词过滤系统的性能要求就要很高。
1、基于单模式串和 Trie 树实现的敏感词过滤
常见的字符串匹配算法,有 BF 算法、RK 算法、BM 算法、KMP 算法,还有 Trie 树。前面四种算法都是单模式串匹配算法,只有 Trie 树是多模式串匹配算法。
单模式串匹配算法,是在一个模式串和一个主串之间进行匹配,也就是说,在一个主串中查找一个模式串。
多模式串匹配算法,就是在多个模式串和一个主串之间做匹配,也就是说,在一个主串中查找多个模式串。
在敏感词需求中,我们可以对敏感词字典进行预处理,构建成 Trie 树结构。预处理只需要做一次,如果敏感词字典动态更新了,比如删除、添加了一个敏感词,那我们只需要动态更新一下 Trie 树就可以了。
2、经典的多模式串匹配算法:AC 自动机
AC自动机比Trie树牛在哪里,其实就是添加了fail指针可以减少匹配次数。
AC 自动机算法,全称是 Aho-Corasick 算法。Trie 树跟 AC 自动机之间的关系,就像单串匹配中朴素的串匹配算法,跟 KMP 算法之间的关系一样,只不过前者针对的是多模式串而已。所以,AC 自动机实际上就是在 Trie 树之上,加了类似 KMP 的 next 数组,只不过此处的 next 数组是构建在树上罢了。
如果代码表示,就是下面这个样子:
public class AcNode {
public char data;
public AcNode[] children = new AcNode[26]; // 字符集只包含 a~z 这 26 个字符
public boolean isEndingChar = false; // 结尾字符为 true
public int length = -1; // 当 isEndingChar=true 时,记录模式串长度
public AcNode fail; // 失败指针
public AcNode(char data) {
this.data = data;
}
}
所以,AC 自动机的构建,包含两个操作:
- 将多个模式串构建成 Trie 树;
- 在 Trie 树上构建失败指针(相当于 KMP 中的失效函数 next 数组)。
3、经典AC自动机代码实现
LeetCode1032是一道典型的多模式匹配题目。
class StreamChecker {
TrieNode root;
TrieNode temp;
public StreamChecker(String[] words) {
root = new TrieNode();
for (String word : words) {
TrieNode cur = root;
for (int i = 0; i < word.length(); i++) {
int index = word.charAt(i) - 'a';
if (cur.getChild(index) == null) {
cur.setChild(index, new TrieNode());
}
cur = cur.getChild(index);
}
cur.setIsEnd(true);
}
root.setFail(root);
Queue<TrieNode> q = new LinkedList<>();
for (int i = 0; i < 26; i++) {
if (root.getChild(i) != null) {
root.getChild(i).setFail(root);
q.add(root.getChild(i));
} else {
root.setChild(i, root);
}
}
while (!q.isEmpty()) {
TrieNode node = q.poll();
node.setIsEnd(node.getIsEnd() || node.getFail().getIsEnd());
for (int i = 0; i < 26; i++) {
if (node.getChild(i) != null) {
node.getChild(i).setFail(node.getFail().getChild(i));
q.offer(node.getChild(i));
} else {
node.setChild(i, node.getFail().getChild(i));
}
}
}
temp = root;
}
public boolean query(char letter) {
temp = temp.getChild(letter - 'a');
return temp.getIsEnd();
}
}
class TrieNode {
TrieNode[] children;
boolean isEnd;
//添加失败指针
TrieNode fail;
public TrieNode() {
children = new TrieNode[26];
}
public TrieNode getChild(int index) {
return children[index];
}
public void setChild(int index, TrieNode node) {
children[index] = node;
}
public boolean getIsEnd() {
return isEnd;
}
public void setIsEnd(boolean b) {
isEnd = b;
}
public TrieNode getFail() {
return fail;
}
public void setFail(TrieNode node) {
fail = node;
}
}
文章介绍了敏感词过滤在大型网站中的应用,主要涉及字符串匹配算法和数据结构。通过使用Trie树和AC自动机,可以高效地在大量用户内容中检测和过滤敏感词。AC自动机在Trie树的基础上添加了fail指针,减少了匹配次数,提升了性能。
1097

被折叠的 条评论
为什么被折叠?



