DFA 算法

2024-05-24 15:36
文章标签 算法 dfa

本文主要是介绍DFA 算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

为什么要学习这个算法

  前一段时间遇到了瓶颈,因为词库太多了导致会有一些速度过慢,而且一个正则表达式已经放不下了,需要进行拆分正则才可以。

正好我以前看过有关 dfa 的介绍,但是并没有深入的进行研究,所以就趁着周末好好的了解一下这个东西。跟 php 的正则进行一下对比,看看速度如何,如果表现较好,说不定还能用得上。

什么是 dfa

通过百度可以知道 dfa 是 确定有穷自动机 的缩写。 应该还会见到类似下面图的说明

原谅我实在一些,我这人数学不好不说,貌似看图能力也不行,这个图恕我直言我没看懂。所以关于精准的解释,请大家去百度或者 google 自行查阅了。

我的理解

说明之前,我们先看看做检测需要准备的东西

  • 一个组织好的关键词树
  • 待检测的字符串

什么是组织好的关键词树

我们一批需要检测词库,比如下面这些

日本人,日本鬼子,日本人傻,破解*版

先做个解释,前三个大家都能看懂,那么 * 是什么,这个是我定义的通配符,代表着 * 可以是 0 - n 个占位符用来替代在关键词中间插入混淆字符。至于可以替换几个我们可以在代码中进行定义,需要注意 n 越大,速度就会越慢。

说明完了,来看看构造好的树是什么一样的,应该是跟下图差不多的。

为什么要手动画一个,因为需要对比,我的理解跟程序是否一致,如果不一致,就要找出程序是不是写的不对了。那么我们来看看程序生成的是啥样的。

程序生成的跟图片一致,到这里还都是正确的。

待检测的字符串

这个就很容易理解了,就是我们需要检测的字符串。

为什么要组织好那样的一棵树(算法思路)

这块需要先说一个概念

它是是通过event和当前的state得到下一个state,即event+state=nextstate

这句话,或者类似的话你会在绝大多数的解释文章里面看到。而我的理解就是,一个字符一个字符的检测,如果检测的字符在我们的树种,就进入命中的树,看下一个字在不在树里面,如果持续的命中就持续进入,最后完全命中了,也就是那个字的子树只有一个元素,并且元素的键是 end (这里是在我们的这个例子中,看图就明白了)。就是完全命中了关键词,就可以记录命中,或者准备替换了。

这里说一个可以优化的点,看我们的例子有两个词 日本人,日本鬼子 这两个,如果为了快,完全可以去掉第二个词,质保流一个就行了,这样当检测到 end 就可以直接屏蔽或者记录了,而在我们的例子中,还需要判断元素数量,不是 1 的情况下还得继续深入,看看是不是命中了长尾。

这样的长尾检测会引发一个问题,那就是 回滚,当我们命中了前置的词,后续的没有命中的时候就得记录并且回滚,这个回滚的长度是是多少呢?其实不仅仅是没有命中长尾的回滚,还有一个 回滚 操作,就是检测率几个字之后就没命中率额,就得回顾,这个回滚的长度是,已检测字符长度 - 1 的长度 。那么没有命中长尾的长度我们就知道了,已检测字符长度 - 上次命中的长度 就可以了。

下面我们来看看代码实现。

// 通配符的数量
$maskMin = 0;
$maskMax = 3;
// 关键词词典字符串,这个部分的处理自己可以替换
$dict = "傻瓜";
$checkDfaTree = [];
$dictArr = explode(',', $dict);
// 重组一下带有 * 通配符的数组
$fullDictArr = [];
foreach ($dictArr as $word) {if (mb_strpos($word, '*') !== false) {// 带有通配符就把通配符去掉for ($maskIndex = $maskMin; $maskIndex <= $maskMax; $maskIndex++) {$maskString = str_pad('', $maskIndex, '*');$inputWord = str_replace('*', $maskString, $word);$fullDictArr[] = $inputWord;}} else {$fullDictArr[] = $word;}
}foreach ($fullDictArr as $word) {// 每次开始新词都要回到树的根部$treeStart = &$checkDfaTree;$wordLen = mb_strlen($word);for ($i = 0; $i < $wordLen; $i++) {$char = mb_substr($word, $i, 1);$treeStart[$char] = isset($treeStart[$char]) ? $treeStart[$char] : [];if ($i + 1 == $wordLen) {// 如果已经是次的结尾了就设置null$treeStart[$char]['end'] = true;}// 移动指针到下一个$treeStart = &$treeStart[$char];}
}
// 遍历str
$start = microtime(true);
$checkMessageLen = mb_strlen($checkMessage);
$wordArr = [];
$checkTreeStart = &$checkDfaTree;
$hasPrefixLength = 0;
$targetWord = '';for ($i = 0; $i < $checkMessageLen; $i++) {// 获取一个字符$char = mb_substr($checkMessage, $i, 1);if (isset($checkTreeStart[$char])) {// 如果有这个字就进入子树里面if (isset($checkTreeStart[$char]['end']) && $checkTreeStart[$char]['end'] === true) {// 如果包含这个标识,就记录标识$hasPrefixLength = mb_strlen($targetWord);}$checkTreeStart = &$checkTreeStart[$char];$targetWord .= $char;} else if (isset($checkTreeStart['*'])) {// 如果有通配符就进入子树$checkTreeStart = &$checkTreeStart['*'];$targetWord .= $char;} else {if ($hasPrefixLength) {$wordArr[] = mb_substr($targetWord, 0, $hasPrefixLength + 1);// 回滚$i -= mb_strlen($targetWord) - $hasPrefixLength;} else {// 回滚$i -= mb_strlen($targetWord);}// 回到头部$checkTreeStart = &$checkDfaTree;$targetWord = '';$hasPrefixLength = 0;}if (count($checkTreeStart) == 1 && isset($checkTreeStart['end']) && $checkTreeStart['end'] === true) {// 子树只有一个并且是end 就说明是命中了// 赋值$wordArr[] = $targetWord;// 清空$targetWord = '';// 回到头部$checkTreeStart = &$checkDfaTree;$hasPrefixLength = 0;}
}
var_dump($wordArr);
echo "<br /> useTime:" . (microtime(true) - $start) * 1000;

下面这个就是匹配加测试了,目前我能想到的都测试通过了,如果有问题,可以回复我。

结论

目前来看,效率是比正则要好一些,命中的情况下速度差不多,没命中的情况下表现要优于正则。

这篇关于DFA 算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/998814

相关文章

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第