php代码实现AC自动机

最近刚回北京来找工作,被百度面试官问到这么一个问题:

有五亿个文件文档,另外还有10万个敏感词,怎么判断这五亿个文件里是否有包含敏感词?.......这个我第一念头真不知道怎么办,最后还是问了面试官,面试官告诉我涉及到了一个算法。那就是AC自动机,我通过百度了一批AC自动机的文章,也大概了解到了它的进化历程......关于这方面的文章原理描述这里就不搬了,就当了解有这么一个算法能应对一些类似开发中的需求就好了,这里就复制了别人的代码备用。

class Node
{
 
    public $value;                 // 节点值
    public $is_end = false;        // 是否为结束--是否为某个单词的结束节点
    public $childNode = array();   // 子节点
    public $fail = 0;               // 失败指针
    public $failIndex = 0;         // 失败数组指针
    public $trie = 0;              // 结构树层级
    public $parent = false;        // 父节点
 
    // 添加孩子节点--注意:可以不为引用函数,因为PHP对象赋值本身就是引用赋值
    public function &addChildNode($value, $is_end = false)
    {
        $node = $this->searchChildNode($value);
        if (empty($node)) {
            // 不存在节点,添加为子节点
            $node = new Node();
            $node->value = $value;
            $node->parent = &$this;
            $node->trie = $this->trie + 1;
            $this->childNode[] = $node;
        }
        if (!$node->is_end) {
            $node->is_end = $is_end;
        }
        return $node;
    }
 
    // 查询子节点
    public function searchChildNode($value)
    {
        foreach ($this->childNode as $k => $v) {
            if ($v->value == $value) {
                // 存在节点,返回该节点
                return $this->childNode[$k];
            }
        }
        return false;
    }
}
 
// 添加字符串
function addString(&$head, $str)
{
    $node = null;
    for ($i = 0; $i < strlen($str); $i++) {
        if ($str[$i] != ' ') {
            $is_end = $i != (strlen($str) - 1) ? false : true; // 如果最后一位就是false;
            if ($i == 0) {
                $node = $head->addChildNode($str[$i], $is_end);
            } else {
                $node = $node->addChildNode($str[$i], $is_end);
            }
        }
    }
}
 
// 获取所有字符串--递归
function getChildString($node, $str_array = array(), $str = '')
{
    if ($node->is_end == true) {
        $str_array[] = $str;
    }
    if (empty($node->childNode)) {
        return $str_array;
    } else {
        foreach ($node->childNode as $k => $v) {
            $str_array = getChildString($v, $str_array, $str . $v->value);
        }
        return $str_array;
    }
}
 
// 字符串多模匹配
function search($p, $head, &$failArray)
{
    $i = 0;
    $res = [];
    while ($i < strlen($p)) {
        $head = searchWords($head, $p[$i], $res, $failArray, $i);
        $i++;
    }
    return $res;
}
 
function searchWords(&$head, $value, &$res, &$failArray, &$i)
{
    foreach ($head->childNode as $k => $v) {
        if ($v->value == $value) {
            // 成功存入
            if ($v->is_end == true) {
                $res[getWords($head->childNode[$k])][] = $i;
            }
            // fail节点也是指向一个结束节点
            if ($failArray[$v->fail]->is_end == true) {
                $res[getWords($failArray[$v->fail])][] = $i;
            }
            // 跳转fail
            if (empty($v->childNode)) {
                return $failArray[$v->fail];
            }
            // 继续下一级匹配
            return $head->childNode[$k];
        }
    }
    // fail指针正在后退,没到root节点主指针不动
    if ($head->failIndex) {
        $i--;
    }
    // 失败指向fail 对比指针不动
    return $failArray[$head->fail];
}
 
// 获取完整字符
function getWords($node)
{
    $str = '';
    while ($node->parent) {
        $str .= $node->value;
        $node = $node->parent;
    }
    return strrev($str);
}
 
// 构造fail指针
function buildFailIndex(&$node, $fail_array = [], &$failTrie)
{
    $fail_array[] = $node;
    if (!isset($failTrie[$node->trie])) {
        $failTrie[] = [];
    }
    ($failTrie[$node->trie])[] = &$node;
    $node->failIndex = count($fail_array) - 1;
    if (empty($node->childNode)) {
        return $fail_array;
    } else {
        foreach ($node->childNode as $k => $v) {
            $fail_array = buildFailIndex($node->childNode[$k], $fail_array, $failTrie);
        }
        return $fail_array;
    }
}
 
// 结构树
function trie(&$failArray, &$failTrie)
{
    foreach ($failTrie as $k => $v) {
        // 层数循环
        foreach ($v as $k1 => $v1) {
            // 每层每个节点循环
            $failTrie[$k][$k1]->fail = buildTrie($failTrie[$k][$k1], $failArray);
        }
 
    }
}
 
function buildTrie(&$node, &$failArray)
{
    if ($node->failIndex == 0 || $node->parent->failIndex === 0) {
        return 0;
    }
     // 循环问题
    foreach ($failArray[$node->parent->fail]->childNode as $k => $v) {
        if ($v->value == $node->value) {
            return $v->failIndex;
        }
    }
    return 0;
}
 
/* 调用测试开始 */
$head = new Node;
 
// 添加单词
addString($head, 'say');
addString($head, 'she');
addString($head, 'sher');
addString($head, 'h');
addString($head, 'her');
// fail二维指针数组(方便遍历)
$failTrie = [];
// 创建一个fail指针数组
$failArray = buildFailIndex($head, [], $failTrie);
trie($failArray, $failTrie);
var_dump(search('hshershrewr', $head, $failArray));
复制代码

摘自:https://blog.csdn.net/weixin_33877092/article/details/91388544?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2

猜你喜欢

转载自www.cnblogs.com/wt645631686/p/13174155.html
今日推荐