[Algoritmo] AC máquina automática / AC algoritmo - la coincidencia de cadenas rápida múltiple


autómata AC

Aceptado

Aho-Corasick


propiedad

Autómata AC / algoritmos de CA (Aho-Corasick autómata), es un conocido algoritmo de coincidencia de varios patrones.


Pre-conocimiento

  1. Trie ( importante )
  2. algoritmo KMP (Siguiente entender el papel de la matriz)

Un ejemplo típico de análisis de algoritmo de complejidad

Un ejemplo típico es: Dada una cadena principal S, una pluralidad de una cadena de patrón T dada, el número Q de un patrón dado existe en la principal cadena de cadena de S

En KMP algoritmo, la complejidad de una longitud de cadena principal de longitud n de una cadena de patrón de m es O (n + m)

Si KMP algoritmo para copiar directamente a estas preguntas, la cadena patrón de proceso de coincidencia necesidad de nuevo

Si hay una cadena de patrón t, la complejidad O ((n + m) t )

Si una gran longitud principal cadena, cuerda o patrón dado mucho, incluso si la complejidad del algoritmo será alto KMP

Así nacido máquina automática de CA que es capaz de O (n + m) para obtener la respuesta a la complejidad de

Que O (mt) gastado en el establecimiento del trie, O (n) a través de la cadena principal que atraviesa la pasó

Por lo que su tiempo de complejidad puede ser controlada dentro de un pequeño rango

(Desventaja heredada de la complejidad de la trie espacio ......)


Sobre Despiste puntero

AC característica más grande es la adición de una máquina automática llama un puntero para cada nodo falle

El papel de este puntero y el papel del algoritmo KMP es muy similar a la matriz siguiente

KMP algoritmo es simplemente para hacer referencia a la siguiente [j] como la siguiente posición de acuerdo a la posición de coincidencia de patrones a juego j

AC es el autómata para cada nodo, Despiste tiene un puntero a la siguiente posición donde coincide

Esta es la razón por la autómata puede ser O (n) para completar el juego principal cadena


Puntero a fallar de configuración:

  Si el ajuste de corrientes en el nodo de trie a un nodo, que corresponde a la-ésimo i posición de la cadena principal, la cadena principal se encontró que i + 1-posición del nodo hijo no existe en el nodo, lo que indica la ocurrencia de una falta de coincidencia del nd nodo

  En este punto tenemos que encontrar y desde el nodo raíz hasta el nodo raíz en este caso compuesto por una cadena sufijo misma más largo prefijo de cadena patrón

  La falla que el puntero de nodo a nodo debe ser el último carácter del prefijo más largo que corresponde a

Se puede encontrar, excepto en circunstancias especiales, un nodo representa un personaje y el personaje representado por su puntero del nodo falla es el mismo


¿Por qué buscar es la misma que su sufijo prefijo más largo -

  Esta es la esencia de KMP algoritmo, entonces el presente procesamiento es un sufijo cadena sufijo esquemática, la cadena de prefijo es un prefijo de otro patrón. Sólo movido a la posición del prefijo más largo con el fin de garantizar que todas las respuestas son de averiguarlo. Desde la perspectiva de KMP punto de vista algoritmo, es decir, tanto como sea posible de amplitud pequeña desplaza a la posición correcta de la cadena de patrón con el fin de garantizar la respuesta correcta algunos no caer.

  En cadena abcabcqabcabc principal, la cadena patrón en abcabcab como un ejemplo, una posición correspondiente a la coincidencia (es decir, la respuesta) con 2:

un si C un si C un si C un si C
un si C un si C un si
un si C un si C un si C un si C
un si C un si C un si

Para la cadena de patrón, el mismo sufijo prefijo donde hay dos tipos: abyabcab

En caso de ser seleccionado en este momento es largo, la amplitud de movimiento es 8-5 = 3, que se mueve a la parte superior de la Tabla 1 Tabla 2

Si más corto prefijo y sufijo seleccionado para el rango de 8-2 cambio = 6, se convertirá

un si C un si C un si C un si C
un si C un si C un si

Debido a que la cadena de patrón más allá del límite, el final del partido, a continuación, sólo registra los resultados de la Tabla 1 caso

Se trata de elementos de próxima KMP representa la mayor longitud de prefijos y sufijos misma razón

autómata de CA está buscando es la misma razón por su prefijo más largo del sufijo


Para más detalles, véase más abajo y el proceso de establecer "los logros del Proceso"




Acerca de abajo

En el siguiente código HDU 2222 Palabras clave Buscar Case

Título significa la pregunta original:

Dado un número de T, T representa un grupo de la muestra, para dar cada uno un número N (N <= 10 000), expresado como una cadena patrón de N palabras

Siguiente N líneas de una palabra, la longitud de palabra de no más de 50

La última línea de las cuerdas principales, la longitud de la cadena no es más de 1 millón de primaria

Para cada muestra, la presencia de la producción de las principales cadena de patrones de cadena de números

(Palabra dado puede ser una repetición, por lo que repetir las palabras como los diferentes modos de cadenas , esta palabra si la cadena principal aparece de nuevo, por supuesto, si el mismo tipo de palabras que aparecen en la cadena principal y el número de veces que la respuesta será repeticiones. más de una vez, la respuesta subsiguiente no contados)

(Ya que es un patrón de encordado han llevado a cabo la respuesta será solo otra vez KMP)




Despiste procedimiento de configuración del puntero (esquema)

Si ahora se encuentran las siguientes cuatro palabras

abs

abi

wasabi

binary

Establecimiento de trie muestra a continuación

un

Completar la construcción del árbol que necesita para empezar la construcción falle puntero

En primer lugar debe tener en cuenta las circunstancias especiales:

  Predeterminada - fallar puntero apunta a la raíz NULL (más tarde identificado como el final de la iteración)

  Puntero al nodo raíz fallan todos los nodos del niño de los puntos de nodo raíz a la presencia de todos (sólo una carta, no es igual que cualquiera de los prefijos y sufijos)

Entonces considere las iteraciones posteriores de caso:

  Supongamos ahora que nodo al nodo de procesamiento

  Si en este momento la misma longitud sufijo prefijo es cero, indicando que no hay presencia de la misma cualquier sufijo prefijo correspondiente, el puntero de nodo debe apuntar a la raíz fallar

  Si en este momento es igual a la misma longitud de sufijo 1, lo que indica la presencia de sólo el nodo representante del mismo prefijo carácter, entonces el puntero de nodo a fallar debe ser adyacente al nodo de sub-raíz, fallan puntero nodo ahora puntos para el nodo raíz de la matriz

  Si la misma longitud de prefijo es mayor que 1 caso sufijo, siempre que el nodo padre de los punteros a nodos dejar de punto a donde debe haber sido dirigida a hacer que el personaje nodo representativo es c, entonces el nodo puede apuntar a fallar directamente fallar nodo del nodo padre nodo hijo señaló al nodo C (si está presente). Si el nodo hijo c es NULL (explicar este nodo no existe), entonces el nodo falla iteración debe continuarlo, para encontrar si hay un camino falla en un nodo que tiene los niños c . Si la última iteración de la raíz puntero falle, es NULL, el fin de la iteración directa, pero esta vez para cambiar manualmente el nodo es la raíz de dejar de señalar en lugar de NULL restante


Para asegurar que la iteración

Es decir, una mayor profundidad de nodos puede fallar puntero base pequeña profundidad del nodo para construir su propia fallar

Y la profundidad se puede encontrar en todos los nodos fallar puntero a una profundidad menor que la nodo original (raíz están más cerca que para referirse a una dirección)

Así que podemos usar para construir una búsqueda en amplitud fallan, comenzó a propagarse desde la raíz


Consideremos en primer lugar de todo el nodo raíz y la primera capa, el siguiente constructo fallan

si

Cuando BFS niño de cada nodo, o se lexicográfico fin de principio a fin completo de la búsqueda de nuevo si hay un personaje que corresponde al nodo hijo

Después de la primera capa de procesados ​​manualmente todos los nodos, todos los nodos de la primera capa es empujado en la cola

Mira Raíz> a-> b esta ruta

Cuando ciclo nodo hijo 'a' del nodo encontrado es un nodo secundario correspondiente al carácter 'b', se somete a tratamiento

'B' es el nodo padre 'a' y 'a' puntero a la raíz falle

Así tenemos que la raíz para que un nodo padre b dejar de observar la raíz para ver si hay un personaje correspondiente se 'b' de los nodos secundarios

Obviamente "binario" es un primer camino 'b', que está ahí, entonces Raíz> a-> 'B' puede ser el punto de falla b "binario" primera 'b' de la

C

Después de fallar el edificio terminado, poner la cola de salida 'b', continuar procesando el siguiente, es decir, Raíz> W-> a 'a' de

Hasta que no hay ningún elemento en la cola, toda la descripción de la figura puntero fallar en todo Construcción

re




Lograr la función de emparejamiento

Después de la finalización de la construcción del árbol y comenzó a fallar para que coincida con la cadena de patrón cadena principal

Dejamos que cada posición final palabra marca correspondiente en el nodo bandera más 1 (bandera inicialmente 0)

Luego, para cada nodo, el valor de bandera o es

mi

Coincide con la cadena patrón como un árbol entero, a partir de la raíz, una cadena principal desde el primer carácter

En cadena principal como "wasabinaryabi" Ejemplo

Apunta al primer personaje principal cadena 'w', correspondiente al nodo raíz encontrada a un nodo que representa el carácter 'W', el puntero al árbol 'w' a partir del movimiento de la raíz

Hasta el sexto carácter 'i' también, una línea de abajo, encontré que la bandera nodo 'i' es 1, una cadena principal para que coincida con esta palabra, añadido después de la bandera de respuesta está configurado para evitar que el número de repeticiones 0

Debido a que el nodo 'i' no tiene nodo hijo, el nodo se procesa próximo 'i' del nodo señaló a fallar, como se muestra, es decir, Raíz> a-> b-> último nodo i, 'i'

valor de bandera Encontrado 1, y este partido tiempo para una palabra, la bandera de respuesta está configurado a 0 después de añadir

El proceso continúa entonces la 'i' de nodos falla, seguido de un proceso intermedio principal cadena "de ciertas piezas" a la esquina inferior derecha de la larga 'y' también, después de la adición del conjunto de respuestas a 0 para continuar

En este momento, 'y' es la falla directamente a la raíz, la raíz continuar directamente a la cadena principal coincidente

Por último, "abi" Después del acabado, la bandera posición original 'i' se ha ajustado a 0, por lo que no contribuyen a la respuesta

Después de la coincidencia de cadenas primaria, respuesta de salida 3, se refiere al proceso de coincidencia wasabi, abi, binarylas tres palabras

Tenga en cuenta que, cada vez que un partido de nodo, es necesario empezar a caminar un camino desde este nodo falla, la bandera en el camino a unirse a todas las respuestas, no habría la siguiente situación

La figura contiene dos palabras: abcdey bcdeste es un caso especial, donde una palabra está completamente contenida dentro de otra palabra, no incluido

F

Suponiendo que la cadena principal es "abcde", y la izquierda de nuevo un error si la ruta apunta a un nodo en cada vez, entonces el último partido terminó único encuentro 'e' este nodo, sólo una respuesta

Pero, obviamente, BCD también se incluye en el "ABCDE", hay que ocuparse de todos los caminos de nuevo falle

Mientras todos los pasos de nuevo, cuando se trata de la 'd' nodos, 'd' de dejar puntos "BCD" la 'd', esta vez a la "BCD" esta bandera para unirse a la respuesta

detallados códigos coinciden, Ver




la implementación del código

En primer lugar, la estructura de nodos variables se define como sigue

struct node
{
    int flag;
    node *next[26],*fail;
};

node *root;

char str[1000050];

En el que el indicador de grabación en el nodo actual es el número de palabras que terminan

la bandera y la siguiente matriz con el significado ordinario diccionario del mismo árbol


logros addNode

void addNode()
{
    node *nd=root;
    int i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        if(nd->next[id]==NULL)
        {
            nd->next[id]=new node;
            nd->next[id]->flag=0;
            for(int j=0;j<26;j++)
                nd->next[id]->next[j]=NULL;
        }
        nd=nd->next[id];
    }
    nd->flag++;
}

  La contribución de la misma manera trie, en este momento no puede ser inicializados los punteros fallan


Construcción fallar buildFailPointer puntero

void buildFailPointer()
{
    queue<node*> q; //bfs容器
    root->fail=NULL; //根节点fail置空
    for(int i=0;i<26;i++)
    {
        if(root->next[i]!=NULL) //第一层所有出现过的节点的fail全部指向root,并加入队列准备搜索
        {
            root->next[i]->fail=root;
            q.push(root->next[i]);
        }
    }
    while(!q.empty())
    {
        node *nd=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(nd->next[i]!=NULL) //如果这个子节点存在
            {
                node *tmp=nd->fail; //tmp储存当前处理的nd->next[i]的父节点的fail指针
                while(tmp!=NULL) //重复迭代
                {
                    if(tmp->next[i]!=NULL) //直到出现某次迭代的节点存在一个子节点,代表的字符与当前处理的nd->next[i]代表的字符相同时,停止迭代
                    {
                        nd->next[i]->fail=tmp->next[i]; //那么当前处理的节点的fail就可以指向迭代到的这个节点的对应子节点
                        break;
                    }
                    tmp=tmp->fail; //如果上述子节点不存在,继续迭代fail指针
                }
                if(tmp==NULL) //如果最后tmp指向NULL,说明最后一次迭代到了root节点且没有找到答案,说明不存在任何前缀与当前的后缀相同,此时让fail指向root节点即可
                    nd->next[i]->fail=root;
                q.push(nd->next[i]); //推入队列
            }
        }
    }
}

  Debido a que el procesamiento de búsqueda de nodo es ND-> siguiente [i], sf es ND-> siguiente [i] es un nodo padre


El árbol principal cadena coincidente, el número de tipos de palabras de consulta pregunta

int query()
{
    node *nd=root,*tmp;
    int ans=0,i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        while(nd->next[id]==NULL&&nd!=root) //如果nd没有字符为id的子节点的话,说明在这里失配,需要迭代指向fail,如果遇到根节点的话则无法继续迭代直接退出
            nd=nd->fail;
        if(nd->next[id]!=NULL) //针对于nd为根节点的情况,只有存在字符为id的子节点才改变nd的指向,否则nd继续保持指向根节点
            nd=nd->next[id];
        tmp=nd; //从nd开始走一遍fail路径,把所有完全包含于当前字符串的单词情况都考虑进来
        while(tmp!=root)
        {
            if(tmp->flag!=0)
            {
                ans+=tmp->flag;
                tmp->flag=0; //一定要置0
            }
            else
                break;
            tmp=tmp->fail;
        }
    }
    return ans;
}

  nd del nodo trie Actualmente señaló, es decir, KMP cadena de patrón cursor algoritmo j

  Cuando camino a pie fallar, si se encuentra con una bandera nodo es 0, se ha pasado (o no es el final de la palabra) antes de que este camino, entonces no es necesario continuar por este camino, y ahorrar tiempo




El código completo HDU-2222

#include<bits/stdc++.h>
using namespace std;

struct node
{
    int flag;
    node *next[26],*fail;
};

node *root;

char str[1000050];

void addNode()
{
    node *nd=root;
    int i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        if(nd->next[id]==NULL)
        {
            nd->next[id]=new node;
            nd->next[id]->flag=0;
            for(int j=0;j<26;j++)
                nd->next[id]->next[j]=NULL;
        }
        nd=nd->next[id];
    }
    nd->flag++;
}

void buildFailPointer()
{
    queue<node*> q;
    root->fail=NULL;
    for(int i=0;i<26;i++)
    {
        if(root->next[i]!=NULL)
        {
            root->next[i]->fail=root;
            q.push(root->next[i]);
        }
    }
    while(!q.empty())
    {
        node *nd=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(nd->next[i]!=NULL)
            {
                node *tmp=nd->fail;
                while(tmp!=NULL)
                {
                    if(tmp->next[i]!=NULL)
                    {
                        nd->next[i]->fail=tmp->next[i];
                        break;
                    }
                    tmp=tmp->fail;
                }
                if(tmp==NULL)
                    nd->next[i]->fail=root;
                q.push(nd->next[i]);
            }
        }
    }
}

int query()
{
    node *nd=root,*tmp;
    int ans=0,i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        while(nd->next[id]==NULL&&nd!=root)
            nd=nd->fail;
        if(nd->next[id]!=NULL)
            nd=nd->next[id];
        tmp=nd;
        while(tmp!=root)
        {
            if(tmp->flag!=0)
            {
                ans+=tmp->flag;
                tmp->flag=0;
            }
            else
                break;
            tmp=tmp->fail;
        }
    }
    return ans;
}

void solve()
{
    root=new node;
    root->flag=0;
    root->fail=NULL;
    for(int i=0;i<26;i++)
        root->next[i]=NULL;
    int n;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",str);
        addNode();
    }
    buildFailPointer();
    scanf("%s",str);
    printf("%d\n",query());
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
        solve();

    return 0;
}

Supongo que te gusta

Origin www.cnblogs.com/stelayuri/p/12578889.html
Recomendado
Clasificación