WhatsApp

  

Algoritmo Knuth-Morris-Pratt (KMP) en Python: Guía Completa, Ejemplos y Mejores Prácticas

Aprende a implementar y optimizar el algoritmo de búsqueda de patrones Knuth-Morris-Pratt (KMP) en Python. Incluye teoría, comparativas, casos de uso reales y trucos de rendimiento.

Algoritmo Knuth-Morris-Pratt (KMP) en Python

Una guía exhaustiva que cubre la teoría, implementación, comparativas y buenas prácticas para usar KMP en proyectos reales.

1. ¿Qué es el algoritmo KMP?

El algoritmo de Knuth-Morris-Pratt (KMP) es una técnica de búsqueda de patrones en cadenas de texto que logra O(n + m) tiempo lineal, donde n es la longitud del texto y m la del patrón. Fue publicado en 1977 por Donald Knuth, James H. Morris y Vaughan Pratt.

KMP evita la retrocesión del índice del texto mediante una tabla de prefijo‑sufijo (también llamada lps – longest proper prefix which is also suffix). Esta tabla indica cuánto podemos desplazar el patrón cuando ocurre una discordancia.

2. Complejidad del algoritmo

  • Tiempo de construcción de la tabla LPS: O(m)
  • Tiempo de búsqueda: O(n)
  • Espacio adicional: O(m) (para la tabla LPS)

En la práctica, KMP se comporta muy bien cuando el patrón contiene repeticiones internas (por ejemplo, "abab"), ya que la tabla LPS permite saltos significativos.

3. Implementación paso a paso en Python

3.1. Construcción de la tabla LPS

def build_lps(pattern: str) -> list[int]:
    """Construye la tabla LPS (Longest Prefix‑Suffix) para *pattern*.
    Args:
        pattern: Cadena del patrón a buscar.
    Returns:
        Lista de enteros con la longitud del prefijo‑sufijo para cada posición.
    """
    m = len(pattern)
    lps = [0] * m
    length = 0  # longitud del prefijo anterior más largo
    i = 1
    while i < m:
        if pattern[i] == pattern[length]:
            length += 1
            lps[i] = length
            i += 1
        else:
            if length != 0:
                length = lps[length - 1]
                # No incrementamos i aquí, volvemos a comparar con el nuevo length
            else:
                lps[i] = 0
                i += 1
    return lps

3.2. Búsqueda del patrón

def kmp_search(text: str, pattern: str) -> list[int]:
    """Devuelve todas las posiciones donde *pattern* aparece en *text* usando KMP.
    Args:
        text: Cadena donde buscar.
        pattern: Patrón a encontrar.
    Returns:
        Lista de índices (0‑based) donde el patrón comienza.
    """
    n, m = len(text), len(pattern)
    if m == 0:
        return []
    lps = build_lps(pattern)
    i = j = 0  # i recorre el texto, j recorre el patrón
    matches = []
    while i < n:
        if text[i] == pattern[j]:
            i += 1
            j += 1
            if j == m:
                matches.append(i - j)
                j = lps[j - 1]  # Continuar buscando posibles solapamientos
        else:
            if j != 0:
                j = lps[j - 1]
            else:
                i += 1
    return matches

Ejemplo de uso:

text = "abxabcabcaby"
pattern = "abcaby"
print(kmp_search(text, pattern))  # salida: [6]

4. KMP vs. Boyer‑Moore y Rabin‑Karp

Boyer‑Moore

  • Mejor rendimiento promedio (sub‑lineal) en textos grandes.
  • Requiere tablas de bad‑character y good‑suffix (más memoria).
  • Peor caso O(n·m) cuando el patrón tiene muchas repeticiones.

Rabin‑Karp

  • Basado en hash, útil para búsqueda múltiple de varios patrones.
  • Promedio O(n + m), peor caso O(n·m) por colisiones de hash.
  • Más sencillo de paralelizar en GPUs.

En entornos donde la longitud del patrón es relativamente pequeña y el texto es grande, KMP ofrece una garantía de tiempo lineal sin depender de heurísticas, lo que lo hace ideal para sistemas embebidos o aplicaciones de tiempo real.

5. Aplicaciones prácticas de KMP

  • Compiladores y analizadores léxicos: detección de tokens y patrones sintácticos.
  • Plataformas de seguridad: escaneo de firmas de malware en flujos de datos.
  • Bioinformática: búsqueda de motivos genéticos en ADN/ARN.
  • Sistemas de logging: filtrado de eventos críticos en tiempo real.

6. Optimización y mejores prácticas

6.1. Reutilizar la tabla LPS

Si vas a buscar el mismo patrón en varios textos, calcula lps una sola vez y reutilízalo. Reduce la complejidad total a O(k·n + m) para k búsquedas.

6.2. Evitar conversiones de tipo innecesarias

Trabaja siempre con str nativas de Python (UTF‑8) y evita .encode()/.decode() dentro del bucle de búsqueda.

6.3. Uso de memoryview para datos binarios

Para textos muy grandes (GBs) en entornos de alto rendimiento, conviértelos a bytes y usa memoryview para evitar copias.

def kmp_search_bytes(text: bytes, pattern: bytes) -> list[int]:
    # implementación idéntica pero usando memoryview para indexación rápida
    ...

7. Solución de problemas comunes

  • Patrón vacío: la función devuelve []. Si deseas que coincida en todas las posiciones, maneja el caso por separado.
  • Unicode y normalización: asegúrate de normalizar ambas cadenas con unicodedata.normalize('NFC', s) antes de buscar.
  • Desbordamiento de índices: la tabla LPS nunca contiene valores mayores que len(pattern)-1, por lo que los accesos son seguros.

8. Compatibilidad y escalabilidad

KMP es independiente de la plataforma; funciona igual en CPython, PyPy, Jython o en entornos de contenedores Docker. Para cargas masivas, considera ejecutar la búsqueda en procesos paralelos usando multiprocessing.Pool o en un clúster Spark con pyspark (aunque la sobrecarga de serialización puede superar los beneficios para patrones muy pequeños).

8.1. Benchmark rápido

import time, random, string
def rand_str(n):
    return ''.join(random.choices(string.ascii_lowercase, k=n))
text = rand_str(10_000_000)
pattern = 'abcde'
start = time.perf_counter()
_ = kmp_search(text, pattern)
print('KMP elapsed:', time.perf_counter() - start)

En máquinas modernas con CPU de 3 GHz, el tiempo suele estar entre 0.05 s y 0.12 s para 10 M de caracteres.

9. Conclusión

El algoritmo Knuth‑Morris‑Pratt sigue siendo una herramienta esencial para la búsqueda de patrones linealmente garantizada. Su implementación en Python es sencilla, pero su comprensión profunda permite adaptarlo a contextos de alto rendimiento, sistemas embebidos o pipelines de datos masivos.

Al combinar KMP con buenas prácticas de gestión de memoria, reutilización de la tabla LPS y paralelismo controlado, puedes obtener una solución de búsqueda robusta, predecible y escalable.



Algoritmo Knuth-Morris-Pratt (KMP) en Python: Guía Completa, Ejemplos y Mejores Prácticas
ASIMOV Ingeniería S. de R.L. de C.V., Emiliano Nava 15 noviembre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Algoritmo de distancia de Levenshtein: teoría, implementación en Python y casos de uso
Guía completa sobre el algoritmo de distancia de Levenshtein, su complejidad, implementación optimizada en Python y comparativas con algoritmos de distancia de cadena.