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 T
y un patrón P
, tengo que encontrar todas las apariencias P
en 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?
fuente
Respuestas:
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 encontraraaa
enaaaaaaaaaaaaaaaaaaaaa
) - lo que realmente necesita para ser rápido para las búsquedas de ese tipo?fuente
Aunque llego un poco tarde para responder esta pregunta, creo que
Z-Algorithm
es 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 encontrarz(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 deS
que coincide con el prefijo des(i)
.s(i)
=ith
sufijo deS
.Los siguientes son los
s(i)
valores paras = 'abaaba'
.Los valores z son respectivamente
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
z
valores 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):
aba
Texto (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$T
sonAhora cual
z(i)
=len(P)
.Ans = 11.
Entonces nuestro patrón está presente enAns-len(P)-1
=7
.-1
es por$
caracter.Ahora, por qué
$
o cualquier carácter especial es importante. ConsideraP = 'aaa'
yT = 'aaaaaaa'
. Sin el carácter especial, todosz(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.fuente
z
es preprocesamiento. Sin embargo, es una buena explicación. Puse unaO(n)
forma de convertir el preprocesamiento KMP en el preprocesamiento Z, debido a esta respuesta. Aquí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.
fuente