Algoritmo de coincidencia de cadenas multipatrón: principio de autómata AC, análisis de complejidad e implementación de código

Coincidencia de cadenas de múltiples patrones

Los escenarios de coincidencia de cadenas de múltiples patrones son comunes cuando algunas plataformas bloquean términos sensibles en los discursos de ciertos usuarios.

Utilice un algoritmo de coincidencia de cadenas para buscar términos confidenciales en el texto y reemplazarlos con "***". Aunque se puede utilizar un algoritmo de coincidencia de cadenas de patrón único para encontrar términos confidenciales uno por uno y luego reemplazarlos, en escenarios reales, si la base de datos de términos confidenciales es grande y hay mucho contenido de texto para comparar, el tiempo de coincidencia será ser demasiado largo, lo que puede llevar a que se tarde mucho en enviar un mensaje. Obviamente, esto conducirá a una experiencia de usuario degradada.

Por lo tanto, se necesita un algoritmo de coincidencia eficiente bajo múltiples cadenas de patrones para hacer frente a este escenario.

Filtrar palabras sensibles según el árbol Trie

El árbol Trie en sí es un algoritmo basado en la coincidencia de cadenas de múltiples patrones, que construye múltiples cadenas de patrones en un árbol Trie. Cuando la cadena de patrón cambia, solo es necesario cambiar el árbol Trie.

Al hacer coincidir la cadena principal, hacemos coincidir el árbol Trie uno por uno comenzando desde el primer carácter de la cadena principal. Cuando coincide un carácter incorrecto, movemos el carácter inicial de la cadena principal hacia atrás un carácter y continuamos haciendo coincidir desde la raíz de el árbol Trie, por lo que primero, solo necesitamos escanear la cadena principal una vez para completar la coincidencia de múltiples cadenas de patrones . La eficiencia es mucho mayor que utilizar la coincidencia de cadenas de patrón único.

Principio del autómata de CA

El algoritmo de coincidencia de cadenas de patrones múltiples basado en el árbol Trie mencionado anteriormente es similar al algoritmo de coincidencia de fuerza bruta en la coincidencia de cadenas de un solo patrón. Sabemos que el algoritmo de coincidencia de fuerza bruta puede mejorar la eficiencia al introducir la siguiente matriz, es decir , el algoritmo KMP, en esta coincidencia de cadenas de múltiples patrones, ¿Deberíamos agregarle la idea de la siguiente matriz?

La respuesta es obviamente sí, solo necesita transformar ligeramente el árbol Trie. Por supuesto, no se trata de agregar la siguiente matriz, sino de agregar una siguiente matriz a cada nodo del árbol Trie.siguiente puntero,Ahora mismopuntero de falla

cComo se muestra en la figura, el puntero de falla del carácter está bcfen la cadena c. Cuando lo emparejamos abc, encontramos que dno coincide con el carácter. En este momento, podemos csaltar al puntero de falla bcdy continuar haciendo coincidir f.
puntero de falla
De esta manera, ya no es necesario comenzar a hacer coincidir nuevamente cuando no hay coincidencia. La idea es la misma que la siguiente matriz. Si no comprende el principio del algoritmo KMP, se recomienda comprender primero la siguiente matriz. Y luego observe el puntero de falla para comprenderlo fácilmente (lectura recomendada del algoritmo KMP: Algoritmo de coincidencia de cadenas famoso: análisis de principios del algoritmo KMP e implementación de código )

La siguiente pregunta es, ¿cómo encontrar el siguiente nodo señalado por el puntero de falla de cada nodo?
El árbol Trie en realidad incluye todas las cadenas de patrón. Supongamos que ahora requerimos cel puntero de falla del nodo en la figura anterior. La condición conocida es que cuando coincide cen todas partes , abces el prefijo el que ha coincidido con éxito con la cadena principal, y luego la cadena de patrón. que debe coincidir Deben ser abcotras cadenas de patrón con la subcadena de sufijo como subcadena de prefijo, y debe ser la subcadena de prefijo coincidente más larga .

abcLas subcadenas de sufijo de cy solo pueden coincidir las subcadenas de prefijo y sufijo bcde otras cadenas de patrón , por lo que el puntero de error debe apuntar a .bcfbcabccbcfc

construir un autómata

Las condiciones para construir un autómata son las siguientes:

  1. Construir un árbol Trie
  2. Inicializar el puntero de falla del nodo

Primero, echemos un vistazo a la estructura de datos de cada nodo:

public class AcNode {
    
    
    public char data; //数据域
    public AcNode[] children = new AcNode[26]; //字符集只包含a~z这26个字符
    public boolean isEndingChar = false; //记录模式串结尾字符
    public int length = -1; //记录模式串长度
    public AcNode fail; // 失败指针
    public AcNode(char data) {
    
    
      this.data = data;
    }
}

Se puede encontrar que, en comparación con el árbol Trie, solo hay uno más.puntero de falla

Por lo tanto, el primer paso en la construcción de un autómata es construir un árbol Trie, que no se discutirá en detalle aquí (consulte Principios de construcción del árbol Trie, escenarios de aplicación y análisis de complejidad ).

La pregunta que debemos considerar ahora es, después de construir el árbol Trie, ¿cómo podemos obtener los indicadores de falla de todos los nodos?

A través del análisis anterior, ya sabemos que para que el puntero de falla de un nodo apunte al nodo, en realidad necesitamos encontrar la subcadena de prefijo más larga que coincida con la subcadena de sufijo de la parte anterior de la cadena de patrón donde se encuentra el nodo .

En un árbol Trie, el puntero de falla de un nodo apunta al nodo solo en su nivel superior. Por lo tanto, el puntero de falla se puede obtener usando el mismo método que la siguiente matriz, es decir, el puntero de falla del nodo actual se puede deducir del nodo donde se obtuvo el puntero de falla.

El puntero de falla de la raíz del nodo raíz es nulo, es decir, apunta a sí mismo, entonces, después de obtener el ppuntero de falla de un determinado nodo q, ¿cómo obtener el puntero de falla de su nodo secundario?

Situación uno: Compare plos nodos secundarios de y qlos nodos secundarios entre sí. Si son iguales, se encontrará el puntero de falla correspondiente.
Insertar descripción de la imagen aquí
Situación 2: Si plos nodos secundarios de qno son iguales a los nodos secundarios de , pasamos qel puntero de falla para obtener el nodo correspondiente y continuamos buscando nodos secundarios hasta encontrar nulo, que es el nodo raíz.
Insertar descripción de la imagen aquí

Aquí está el código para construir el puntero fallido:

public void buildFailurePointer(AcNode root) {
    
    
    Queue<AcNode> queue = new LinkedList<>();
    root.fail = null;
    queue.add(root);
    while (!queue.isEmpty()) {
    
    
        AcNode p = queue.remove();//拿到节点p
        for (AcNode pc : p.children) {
    
    //遍历节点p的子节点
            if (pc == null) continue;
            if (p == root) {
    
    //root的子节点失败指针为root
                pc.fail = root;
            } else {
    
    
                AcNode q = p.fail;//找到p的失败指针节点q
                while (q != null) {
    
    
                	//查找p的子节点是否存在q的子节点
                    AcNode qc = q.children[pc.data - 'a'];
                    if (qc != null) {
    
    //存在则找到失败指针
                        pc.fail = qc;
                        break;
                    }
                    q = q.fail;//否则继续找下一个失败指针
                }
                if (q == null) {
    
    //直到找到null,则失败指针为root
                    pc.fail = root;
                }
            }
            queue.add(pc);
        }
    }
}

Después de construir el puntero de falla, como se muestra en la figura:
Insertar descripción de la imagen aquí

Utilice la combinación de autómatas de CA

Suponiendo la cadena principal str, la coincidencia comienza desde el primer carácter de la cadena principal y el autómata p=rootcomienza a coincidir desde el puntero.

  1. Supongamos pque el nodo secundario xes igual a str[0], luego pactualice a xy luego verifique si el puntero de falla p(actualmente apuntado a x) es el final de una cadena de patrón. Si es así, busque una cadena de patrón coincidente. Después del procesamiento, continúe haciendo coincidir str [2].
  2. Si después de alcanzar un determinado paso, pno se encuentran caracteres coincidentes en los nodos secundarios, entonces el puntero de falla es útil, es decir, buscar en los nodos secundarios del nodo señalado por el puntero de falla.
public void match(char[] str, AcNode root) {
    
     // str是主串,root是自动机
    AcNode p = root;
    for (int i = 0; i < str.length; i++) {
    
    
        int idx = str[i] - 'a';
        //p的子节点中没有,就往p的失败节点的子节点中找,直到失败指针指向null为止
        while (p.children[idx] == null && p != root) {
    
    
            p = p.fail; // 失败指针发挥作用的地方
        }
        p = p.children[idx];//找到匹配的字符后,p更新指向到这个节点
        if (p == null)// 如果没有匹配的,从 root 开始重新匹配
            p = root; 
        AcNode tmp = p;
        while (tmp != root) {
    
     // 找到已经匹配到的模式串
            if (tmp.isEndingChar == true) {
    
    
                int pos = i - tmp.length + 1;
                System.out.println(" 匹配起始下标 " + pos + "; 长度 " + tmp.length);
            }
            tmp = tmp.fail;
        }
    }
}

Eficiencia de coincidencia de autómatas de CA

  1. La complejidad de la construcción del árbol Trie es O(m*len), donde mes el número de cadenas de patrones y lenes la longitud promedio de las cadenas de patrones.
  2. Al construir un puntero de falla, lo que lleva más tiempo es buscar el puntero de falla capa por capa en el ciclo while. Cada ciclo sube al menos una capa y la altura del árbol no excede, por lo que el tiempo lenLa complejidad es O(K*len), K es el nodo en el árbol Trie.
  3. Los dos pasos anteriores solo deben ejecutarse una vez para completar la construcción, lo que no afecta la eficiencia de la coincidencia con la cadena principal. Durante la coincidencia, lo que consume más tiempo es también el código para el siguiente puntero de falla en el ciclo while. Entonces, la complejidad del tiempo es, si O(len)la longitud de la cadena principal es n, entonces la complejidad del tiempo total de coincidencia esO(n*len)

De hecho, al hacer coincidir palabras sensibles, la longitud promedio de las palabras sensibles no será muy larga. Por lo tanto, la eficiencia de coincidencia del autómata AC es muy cercana. Sólo en casos extremos, la eficiencia se O(n)degradará a la misma que la eficiencia de coincidencia del árbol Trie. .

Los casos extremos son los siguientes:
Autómata de CA extremo

Supongo que te gusta

Origin blog.csdn.net/m0_37264516/article/details/86177992
Recomendado
Clasificación