¿Qué algoritmo de búsqueda de cadenas es realmente el más rápido?

27

He estado atrapado durante algún tiempo en cuál es el algoritmo de búsqueda de cadenas más rápido, escuché muchas opiniones, pero al final no estoy seguro.

He escuchado a algunas personas decir que el algoritmo más rápido es Boyer-Moore y algunos dicen que Knuth-Morris-Pratt es en realidad más rápido.

He buscado la complejidad en ambos, pero en su mayoría se ven iguales O(n+m). He descubierto que en el peor de los casos, Boyer-Moore tiene una O(nm)complejidad en comparación con Knuth-Morris-Pratt que tiene O (m + 2 * n). Donde n = longitud del texto ym = longitud del patrón.

Hasta donde yo sé, Boyer-Moore tiene un peor tiempo lineal si usara la Regla de Galil.

Mi pregunta, sobre todo, que en realidad es el algoritmo de búsqueda de cadenas más rápido (esta pregunta incluye todos los algoritmos de picadura posibles, no solo Boyer-Moore y Knuth-Morris-Pratt).

Editar: debido a esta respuesta

Lo que estoy buscando exactamente es:

Dado un texto Ty un patrón P, tengo que encontrar todas las apariencias Pen T.

También la longitud de P y T es de [1,2 000 000]y el programa tiene que ejecutarse por debajo de 0,15 segundos.

Sé que KMP y Rabin-Karp son suficientes para obtener un puntaje del 100% en el problema, pero por mi parte quería intentar implementar Boyer-Moore. ¿Cuál sería el mejor para este tipo de búsqueda de patrones?

vandamon taigi
fuente
66
Cuando probaste esto en tu idioma de elección, ¿qué encontraste?
Walter
44
En algunas pruebas, Boyer-Moore fue mejor en otras KMP fue mejor, pero no estoy seguro de tener la "mejor" implementación de ellas. En cuanto al idioma de elección, está en las etiquetas: C ++ (no estoy seguro si lo vio desde que escribió "idioma de elección"). PD: Tampoco estoy seguro si probé en las mejores pruebas.
vandamon taigi
1
stackoverflow.com/q/3183582
Robert Harvey
Knuth-Morris-Pratt que tiene O (m + 2 * n) ... Te refieres a O (m + n).
Jules
Elija uno con una complejidad algorítmica decente y luego realice un microajuste con un perfilador en la mano, siempre funcionó para mí. :-D

Respuestas:

38

Depende del tipo de búsqueda que desee realizar. Cada uno de los algoritmos funciona particularmente bien para ciertos tipos de búsqueda, pero no ha indicado el contexto de sus búsquedas.

Aquí hay algunas ideas típicas sobre los tipos de búsqueda:

  • Boyer-Moore: trabaja analizando previamente el patrón y comparando de derecha a izquierda. Si se produce una falta de coincidencia, el análisis inicial se utiliza para determinar qué tan lejos se puede desplazar el patrón con respecto al texto que se busca. Esto funciona particularmente bien para patrones de búsqueda largos. En particular, puede ser sub-lineal, ya que no necesita leer todos los caracteres de su texto.

  • Knuth-Morris-Pratt: también preanaliza el patrón, pero trata de reutilizar lo que ya estaba emparejado en la parte inicial del patrón para evitar tener que rehacer eso. Esto puede funcionar bastante bien si su alfabeto es pequeño (por ejemplo, bases de ADN), ya que tiene una mayor probabilidad de que sus patrones de búsqueda contengan subpatrones reutilizables.

  • Aho-Corasick: necesita mucho preprocesamiento, pero lo hace para varios patrones. Si sabe que buscará los mismos patrones de búsqueda una y otra vez, entonces esto es mucho mejor que el otro, porque necesita analizar patrones solo una vez, no una vez por búsqueda.

Por lo tanto, como es habitual en CS, no hay una respuesta definitiva a lo mejor en general . Es más bien una cuestión de elegir la herramienta adecuada para el trabajo en cuestión.

Otra nota sobre su razonamiento en el peor de los casos: considere los tipos de búsquedas necesarias para crear ese peor de los casos y piense detenidamente si estos son realmente relevantes en su caso. Por ejemplo, el O(mn)peor de los casos la complejidad del algoritmo de Boyer-Moore se deriva de un patrón de búsqueda y un texto que cada uso sólo un carácter (como encontrar aaaen aaaaaaaaaaaaaaaaaaaaa) - lo que realmente necesita para ser rápido para las búsquedas de ese tipo?

Franco
fuente
Tengo todo el alfabeto inglés para usar y actualicé la pregunta, lo siento por no comenzar con esto al principio.
vandamon taigi
Y sí, necesito ser rápido incluso para búsquedas como esa
vandamon taigi
1

Aunque llego un poco tarde para responder esta pregunta, creo que Z-Algorithmes mucho más rápido que cualquiera de sus contrapartes. Su peor complejidad es O (m + n) y no requiere preprocesamiento del patrón / texto. También es muy fácil de codificar en comparación con los otros algoritmos.

Funciona de la siguiente manera.

Por ejemplo, hay una cadena S ='abaaba'. Debemos encontrar z(i)valores parai=0 to len(S)-1 . Antes de entrar en la explicación, permítanme poner algunas definiciones primero.

z(i)= no. de caracteres del prefijo de Sque coincide con el prefijo de s(i).

s(i)= ithsufijo de S.

Los siguientes son los s(i)valores para s = 'abaaba'.

s(0) = 'abaaba' = S
s(1) = 'baaba'
s(2) = 'aaba'
s(3) = 'aba'
s(4) = 'ba'
s(5) = 'a'

Los valores z son respectivamente

z(0) = 6 = length(S)
z(1) = 0
z(2) = 1
z(3) = 3
z(4) = 0
z(5) = 1

Para una comprensión detallada del algoritmo, consulte los siguientes enlaces.

http://codeforces.com/blog/entry/3107

https://www.youtube.com/watch?v=MFK0WYeVEag

Ahora se necesita O (N) para encontrar todos los zvalores sin ninguna sobrecarga de preprocesamiento. Uno se estaría preguntando ahora, ¿cómo puede usar esta lógica para unir patrones en una cadena dada?

Veamos con un ejemplo. Patrón (P): abaTexto (T):aacbabcabaad .

Ponga esto en la forma P $ T. ( $- cualquier carácter que no aparece en el patrón o el texto. Llegaré a la importancia de $dentro de poco).

P$T = aba$aacbabcabaad

Sabemos len(P)= 3.

Todos los valores z de P$Tson

z(0) = 16 = len(P$T)
z(1) = 0
z(2) = 1
z(3) = 0
z(4) = 1
z(5) = 1
z(6) = 0
z(7) = 0
z(8) = 2
z(9) = 0
z(10) = 0
z(11) = 3
z(12) = 0
z(13) = 1
Z(14) = 1
Z(15) = 0

Ahora cual z(i)= len(P). Ans = 11.Entonces nuestro patrón está presente en Ans-len(P)-1= 7. -1es por $caracter.

Ahora, por qué $o cualquier carácter especial es importante. Considera P = 'aaa'y T = 'aaaaaaa'. Sin el carácter especial, todos z(i)tendrán valores incrementales. Todavía se puede encontrar la posición del patrón en el texto con las siguientes fórmulas:

Condición: z(i)> = len(P)y Posición: Ans-len(P). Pero la condición en este caso se vuelve un poco complicada y confusa. Personalmente prefiero usar la técnica especial de personajes.

SohamC
fuente
1
¿Podría explicarlo usted mismo aquí? Tener enlaces a sitios externos se puede utilizar para elaborar, pero el núcleo de una respuesta debe estar en la respuesta misma en lugar de tener que seguir un enlace a otro sitio.
El algoritmo z es básicamente el mismo que kmp. Dudo que sea mucho más rápido.
Thomas Ahle
2
Estoy de acuerdo con @ThomasAhle. La computación z es preprocesamiento. Sin embargo, es una buena explicación. Puse una O(n)forma de convertir el preprocesamiento KMP en el preprocesamiento Z, debido a esta respuesta. Aquí
leewz
-1

Utilice memoria direccionable de contenido , implementada en software en forma de direccionamiento virtual (apuntando letras a letras)

Es algo superfluo para un algoritmo promedio de coincidencia de cadenas.

CAM puede coincidir con una gran cantidad de patrones simultáneamente, hasta unos patrones de 128 letras (si son ASCII; si son Unicode solo 64). Y es una llamada por longitud de letra en la cadena con la que desea hacer coincidir y una lectura aleatoria de la memoria por longitud de la longitud máxima del patrón. Entonces, si estuviera analizando una cadena de 100,000 letras, con hasta 90,000,000 patrones simultáneamente (lo que tomaría alrededor de 128 GiB para almacenar un recuento de patrones tan grandes), tomaría 12,800,000 lecturas aleatorias de la RAM, por lo que sucedería en 1 ms.

Así es como funciona el direccionamiento virtual.

Si empiezo con 256 direcciones iniciales, que representan la primera letra, estas letras apuntan a 256 de las siguientes. Si un patrón no existe, no lo almacena.

Entonces, si sigo vinculando letras a letras, es como tener 128 rebanadas de direccionamiento virtual apuntando a direccionamiento virtual.

Eso funcionará, pero para lograr que 900,000,000 patrones coincidan simultáneamente, hay un último truco para agregarle, y se está aprovechando del hecho de que comienzas con una gran cantidad de reutilización de estos buffers de letras, pero luego se dispersa. Si enumeras el contenido, en lugar de asignar los 256 caracteres, se ralentiza muy poco y obtendrás un aumento de capacidad de 100 veces, porque básicamente solo obtienes 1 letra en cada búfer de puntero de letras (que denominé ' escapar').

Si desea obtener una coincidencia de cadena del vecino más cercano, entonces tiene muchos de estos ejecutándose en paralelo y los recopila en una jerarquía, por lo que difunde su error de manera imparcial. si intentas al vecino más cercano con solo uno, entonces estás sesgado hacia el inicio del árbol.

rouncer81
fuente
44
@MagnusRobertCarlWoot dado que tiene el mismo gavatar que roucer81, es una coincidencia astronómica de colisión de código hash o tiene la misma dirección de correo electrónico. Si usted es la misma persona detrás de ambas cuentas, debe usar el formulario "contáctenos" para fusionarlas y obtener el crédito adecuado por la reputación obtenida a través de votos a favor en esta respuesta.