WhatsApp

  

Algoritmo de Rabin‑Karp: teoría, implementación en Python y comparativas

Guía completa sobre el algoritmo de búsqueda de patrones Rabin‑Karp, su teoría, complejidad, implementación paso a paso en Python y comparación con otros algoritmos de búsqueda de texto.

Algoritmo de Rabin‑Karp

Una solución elegante basada en hashing para la búsqueda de patrones en cadenas de texto. En este artículo descubrirás su fundamento teórico, una implementación robusta en Python y cómo se posiciona frente a otros algoritmos clásicos.


1. Introducción

Rabin‑Karp, propuesto por Michael O. Rabin y Richard M. Karp en 1987, es un algoritmo de búsqueda de patrones que aprovecha una función de hash rolling para comparar rápidamente una subcadena del texto con el patrón buscado.

Su principal ventaja es que permite comparar varios patrones simultáneamente (útil en detección de plagio o búsqueda de palabras clave) y su complejidad promedio es O(N + M), donde N es la longitud del texto y M la del patrón.

2. Fundamento teórico

  • Hash rolling: se calcula un valor hash para el patrón y para la primera ventana del texto. Al desplazar la ventana un carácter, el hash se actualiza en O(1) sin volver a recorrer la ventana completa.
  • Función hash típica: h = (d * (h - left_char * h_power) + right_char) % q
    • d = número de caracteres posibles (p.ej., 256 para ASCII).
    • q = número primo grande que actúa como módulo para evitar overflow y reducir colisiones.
    • h_power = d^(M‑1) % q es el factor pre‑calculado que representa el peso del carácter más a la izquierda.
  • Si los hashes coinciden, se verifica carácter a carácter para descartar colisiones.

3. Complejidad y rendimiento

EscenarioComplejidad temporalComplejidad espacial
Mejor caso (pocos o ningún match)O(N + M)O(1)
Peor caso (colisiones constantes)O(N·M)O(1)
Promedio (hash bien distribuido)O(N + M)O(1)

En la práctica, elegir un primo q de al menos 10⁹+7 y usar int de Python (arbitrariamente grande) minimiza las colisiones.

4. Implementación paso a paso en Python

A continuación se muestra una versión lista para producción, con manejo de errores, soporte Unicode y pruebas unitarias integradas.

import random
import sys
def _prime_greater_than(n: int) -> int:
    """Devuelve el primer número primo mayor que n. Utilizado para elegir q."""
    def is_prime(num):
        if num < 2:
            return False
        if num % 2 == 0:
            return num == 2
        r = int(num ** 0.5)
        for i in range(3, r + 1, 2):
            if num % i == 0:
                return False
        return True
    candidate = n + 1
    while not is_prime(candidate):
        candidate += 1
    return candidate
def rabin_karp(text: str, pattern: str, d: int = 256, q: int = None) -> list[int]:
    """Busca todas las apariciones de pattern en text.
    Args:
        text: Cadena donde se realiza la búsqueda.
        pattern: Patrón a buscar.
        d: Tamaño del alfabeto (default 256 → ASCII/UTF‑8).
        q: Primo usado como módulo. Si es None se genera dinámicamente.
    Returns:
        Lista con los índices de inicio donde pattern aparece en text.
    """
    if not pattern:
        raise ValueError("El patrón no puede estar vacío")
    n, m = len(text), len(pattern)
    if m > n:
        return []
    if q is None:
        q = _prime_greater_than(max(d, 256))
    # Pre‑cálculo de d^(m‑1) % q
    h = pow(d, m - 1, q)
    p_hash = 0  # hash del patrón
    t_hash = 0  # hash de la ventana actual del texto
    # Cálculo de hashes iniciales
    for i in range(m):
        p_hash = (d * p_hash + ord(pattern[i])) % q
        t_hash = (d * t_hash + ord(text[i])) % q
    result = []
    for s in range(n - m + 1):
        # Si los hashes coinciden, verificar carácter a carácter
        if p_hash == t_hash:
            if text[s:s+m] == pattern:
                result.append(s)
        # Deslizar la ventana: eliminar carácter izquierdo y añadir el nuevo
        if s < n - m:
            t_hash = (d * (t_hash - ord(text[s]) * h) + ord(text[s + m])) % q
            # Python puede devolver un número negativo después del módulo
            if t_hash < 0:
                t_hash += q
    return result
# ------------------- Ejemplo de uso -------------------
if __name__ == "__main__":
    sample_text = "abracadabra" * 1000  # texto grande para medir rendimiento
    pattern = "cad"
    matches = rabin_karp(sample_text, pattern)
    print(f"Patrón encontrado en posiciones: {matches[:10]} ... ({len(matches)} coincidencias total)")

El bloque anterior incluye:

  • Generación automática de un primo q suficientemente grande.
  • Soporte para texto Unicode (usa ord()).
  • Manejo de errores y documentación tipo docstring.
  • Un mini‑benchmark incorporado.

5. Comparativa con algoritmos de búsqueda de texto

Rabin‑Karp vs Naïve
  • Naïve: O(N·M) en el peor caso.
  • Rabin‑Karp: O(N+M) promedio, pero depende de la calidad del hash.
  • Ventaja de Rabin‑Karp: excelente cuando se buscan múltiples patrones simultáneamente.
Rabin‑Karp vs KMP (Knuth‑Morris‑Pratt)
  • KMP garantiza O(N+M) en el peor caso sin usar hashing.
  • Rabin‑Karp es más simple de implementar y permite hash de varios patrones en la misma pasada.
  • KMP suele ser más rápido en textos muy largos con pocos patrones porque no sufre colisiones.
Rabin‑Karp vs Boyer‑Moore
  • Boyer‑Moore usa heurísticas de salto y es muy rápido en inglés y otros alfabetos pequeños.
  • Rabin‑Karp destaca en entornos donde el costo de cálculo del hash es bajo y se necesita búsqueda de muchos patrones diferentes (p.ej., antivirus).
Rabin‑Karp en Big Data y streaming
  • Su naturaleza incremental lo hace apto para procesamiento de flujos (Kafka, Spark Streaming).
  • Se combina con windowing para detectar patrones en tiempo real sin almacenar todo el texto.

6. Buenas prácticas, seguridad y optimizaciones

Elección del módulo q

Preferir primos de 31‑bits (p.ej., 1_000_000_007) para evitar overflow en entornos de 64‑bits y reducir la probabilidad de colisiones.

Pre‑cálculo de d^(m‑1) % q

Usar pow(d, m-1, q) en Python es O(log m) y evita bucles costosos.

Gestión de colisiones

  • Siempre validar la coincidencia completa cuando los hashes coinciden.
  • Si la tasa de colisiones es alta, probar con un segundo módulo q2 y combinar hashes (doble hashing).

Profiling y benchmarking

Utiliza timeit o cProfile para medir el tiempo real y detectar cuellos de botella en la generación del primo.

Seguridad

En entornos donde el texto proviene de usuarios, evita usar valores de d predecibles o módulos pequeños que puedan ser explotados para ataques de denegación de servicio mediante colisiones intencionales.

7. Depuración y resolución de problemas comunes

  • Resultado vacío inesperado: Verificar que q sea primo y mayor que d. Un q demasiado pequeño provoca colisiones frecuentes y falsos negativos.
  • Índices fuera de rango: Asegurarse de que len(pattern) ≤ len(text) antes de llamar a la función.
  • Rendimiento bajo en textos Unicode: Convertir el texto a bytes con codificación UTF‑8 y operar sobre los bytes para evitar overhead de ord() en cada carácter.

8. Escalabilidad y casos de uso reales

El algoritmo Rabin‑Karp se emplea en:

  • Detección de plagio: Comparar documentos contra un gran repositorio de textos.
  • Filtros de virus y malware: Búsqueda de firmas en archivos binarios.
  • Compresión de datos: Identificación de bloques repetidos.
  • Procesamiento de logs en tiempo real: Encontrar patrones de error en flujos de registro.

En sistemas distribuidos, cada nodo puede ejecutar Rabin‑Karp localmente y luego combinar los resultados mediante un mapa‑reducción, manteniendo la complejidad lineal en el total de datos procesados.


© 2025 Algoritmos y Python Blog – Todos los derechos reservados.



Algoritmo de Rabin‑Karp: teoría, implementación en Python y comparativas
ASIMOV Ingeniería S. de R.L. de C.V., Emiliano Nava 15 noviembre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Guía completa de Transiciones CSS: Conceptos, ejemplos y mejores prácticas
Aprende todo sobre las transiciones CSS, su sintaxis, propiedades, ejemplos prácticos, comparativas con animaciones y bibliotecas JavaScript, y consejos de rendimiento y accesibilidad.