AC自动机 php代码实现

以下文字都为拷贝,懒,没有第二个原因!

ac自动机很神奇,在于这个算法中失配指针的妙处(好比kmp算法中的next数组),说它高深,是因为这个不是一般的算法,而是建立在两个普通算法的基础之上,而这两个算法就是kmp与字典树。所以,如果在看之前,你还不会字典树或者kmp算法,那么请先学习字典树或者kmp算法之后再来看

理论请前往这里,反正很多都可以百度

kmp走起

trie树 数据结构

代码质量不高,还请大牛帮忙给优化

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));
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值