• 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

  • 相关阅读:
    vue点击元素变色兄弟元素不变色
    获取今天昨天本月的时间段
    java.io.InputStream -- 1.8 初识,应用场景待更新
    java.io.FilterInputStream
    java.io.FileInputStream
    java.io.ByteArrayInputStream -- 1.8
    JavaBeans -- 1.8
    mysql 导出和导入数据
    tp5 数据库迁移工具 migrate&seed
    tp5模型一对一关联hasOne
  • 原文地址:https://www.cnblogs.com/wt645631686/p/13174155.html
Copyright © 2020-2023  润新知