Rabin-Karp em Zig — Implementação e Explicação

Rabin-Karp em Zig — Implementação e Explicação

O algoritmo Rabin-Karp usa hashing para buscar padrões em textos. Ele calcula o hash do padrão e o compara com hashes de substrings do texto usando um rolling hash que permite atualização incremental em O(1).

Como Funciona

  1. Calcula o hash do padrão
  2. Calcula o hash da primeira janela do texto (mesma largura do padrão)
  3. Desliza a janela, atualizando o hash incrementalmente
  4. Quando os hashes coincidem, verifica caractere a caractere (para evitar falsos positivos)

Rolling Hash

hash("abc") = a*d² + b*d + c    (d = base, mod p)
hash("bcd") = (hash("abc") - a*d²) * d + d

Visualização

Texto:  "ABCABCDABCD"   Padrão: "ABCD"   base=256, mod=101

Hash do padrão "ABCD" = (65*256³ + 66*256² + 67*256 + 68) mod 101 = ?

Janela 1: "ABCA" → hash ≠ hash_padrão
Janela 2: "BCAB" → hash ≠ hash_padrão  (rolling: remove A, adiciona B)
Janela 3: "CABC" → hash ≠ hash_padrão
Janela 4: "ABCD" → hash == hash_padrão → verifica → MATCH na posição 3!
Janela 5: "BCDA" → hash ≠ hash_padrão
...
Janela 8: "ABCD" → hash == hash_padrão → verifica → MATCH na posição 7!

Implementação em Zig

const std = @import("std");
const Allocator = std.mem.Allocator;

const BASE: u64 = 256;
const MOD: u64 = 1_000_000_007;

/// Busca todas as ocorrências do padrão no texto usando Rabin-Karp.
pub fn rabinKarp(
    allocator: Allocator,
    texto: []const u8,
    padrao: []const u8,
) ![]usize {
    var ocorrencias = std.ArrayList(usize).init(allocator);

    const n = texto.len;
    const m = padrao.len;
    if (m == 0 or n < m) return try ocorrencias.toOwnedSlice();

    // Calcula BASE^(m-1) mod MOD
    var h: u64 = 1;
    for (0..m - 1) |_| {
        h = (h * BASE) % MOD;
    }

    // Calcula hash do padrão e da primeira janela
    var hash_padrao: u64 = 0;
    var hash_texto: u64 = 0;
    for (0..m) |i| {
        hash_padrao = (hash_padrao * BASE + padrao[i]) % MOD;
        hash_texto = (hash_texto * BASE + texto[i]) % MOD;
    }

    // Desliza a janela pelo texto
    for (0..n - m + 1) |i| {
        if (hash_padrao == hash_texto) {
            // Verifica caractere a caractere (evita falsos positivos)
            if (std.mem.eql(u8, texto[i .. i + m], padrao)) {
                try ocorrencias.append(i);
            }
        }

        // Atualiza rolling hash para próxima janela
        if (i < n - m) {
            hash_texto = ((hash_texto + MOD - (texto[i] * h) % MOD) * BASE + texto[i + m]) % MOD;
        }
    }

    return try ocorrencias.toOwnedSlice();
}

/// Busca múltiplos padrões simultaneamente (vantagem do Rabin-Karp).
pub fn rabinKarpMultiplo(
    allocator: Allocator,
    texto: []const u8,
    padroes: []const []const u8,
) ![]struct { padrao_idx: usize, posicao: usize } {
    const ResultMatch = struct { padrao_idx: usize, posicao: usize };
    var resultados = std.ArrayList(ResultMatch).init(allocator);

    for (padroes, 0..) |padrao, idx| {
        const matches = try rabinKarp(allocator, texto, padrao);
        defer allocator.free(matches);
        for (matches) |pos| {
            try resultados.append(.{ .padrao_idx = idx, .posicao = pos });
        }
    }

    return try resultados.toOwnedSlice();
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Exemplo simples
    const texto = "ABCABCDABCDAB";
    const padrao = "ABCD";

    try stdout.print("Texto:  \"{s}\"\n", .{texto});
    try stdout.print("Padrão: \"{s}\"\n\n", .{padrao});

    const matches = try rabinKarp(allocator, texto, padrao);
    defer allocator.free(matches);

    try stdout.print("Ocorrências: {d}\n", .{matches.len});
    for (matches) |pos| {
        try stdout.print("  Posição {d}\n", .{pos});
    }

    // Busca múltipla
    try stdout.print("\n--- Busca múltipla ---\n", .{});
    const padroes = [_][]const u8{ "ABC", "CD", "DAB" };
    const multi = try rabinKarpMultiplo(allocator, texto, &padroes);
    defer allocator.free(multi);

    for (multi) |r| {
        try stdout.print("  Padrão \"{s}\" na posição {d}\n", .{
            padroes[r.padrao_idx], r.posicao,
        });
    }
}

Análise de Complexidade

CasoTempoEspaço
Melhor/MédioO(n + m)O(1)
Pior casoO(n * m) — muitas colisões de hashO(1)

Rabin-Karp vs KMP

AspectoRabin-KarpKMP
Caso médioO(n + m)O(n + m)
Pior casoO(n * m)O(n + m)
Múltiplos padrõesEficientePrecisa de repetição
ImplementaçãoMais simplesMais complexa

Quando Usar

  • Múltiplos padrões: a principal vantagem sobre KMP
  • Detecção de plágio: comparar hashes de substrings de documentos
  • Busca em grandes textos: quando colisões são raras na prática

Recursos Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.