以下文字都为拷贝,懒,没有第二个原因!
ac自动机很神奇,在于这个算法中失配指针的妙处(好比kmp算法中的next数组),说它高深,是因为这个不是一般的算法,而是建立在两个普通算法的基础之上,而这两个算法就是kmp与字典树。所以,如果在看之前,你还不会字典树或者kmp算法,那么请先学习字典树或者kmp算法之后再来看
代码质量不高,还请大牛帮忙给优化
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));
复制代码