Busca Linear em Zig — Implementação e Explicação

Busca Linear em Zig — Implementação e Explicação

A Busca Linear (ou busca sequencial) é o algoritmo de busca mais simples. Ela percorre cada elemento da coleção sequencialmente até encontrar o elemento desejado ou chegar ao final sem encontrá-lo.

Como Funciona

  1. Comece pelo primeiro elemento
  2. Compare o elemento atual com o valor procurado
  3. Se for igual, retorne a posição
  4. Se não for, avance para o próximo elemento
  5. Se chegar ao final sem encontrar, retorne “não encontrado”

Visualização do Processo

Array: [14, 33, 27, 10, 35, 19, 42, 44]
Procurando: 35

Passo 1: [14] 33  27  10  35  19  42  44  → 14 ≠ 35
Passo 2:  14 [33] 27  10  35  19  42  44  → 33 ≠ 35
Passo 3:  14  33 [27] 10  35  19  42  44  → 27 ≠ 35
Passo 4:  14  33  27 [10] 35  19  42  44  → 10 ≠ 35
Passo 5:  14  33  27  10 [35] 19  42  44  → 35 = 35 ✓

Encontrado na posição 4!

Implementação em Zig

const std = @import("std");

/// Busca linear genérica — retorna o índice do elemento ou null.
pub fn buscaLinear(comptime T: type, items: []const T, alvo: T) ?usize {
    for (items, 0..) |item, i| {
        if (item == alvo) return i;
    }
    return null;
}

/// Busca linear com comparador personalizado.
pub fn buscaLinearBy(
    comptime T: type,
    items: []const T,
    comptime iguais: fn (T, T) bool,
    alvo: T,
) ?usize {
    for (items, 0..) |item, i| {
        if (iguais(item, alvo)) return i;
    }
    return null;
}

/// Busca linear com sentinela — evita verificação de limite a cada iteração.
/// Requer que o slice seja mutável para colocar o sentinela no final.
pub fn buscaLinearSentinela(comptime T: type, items: []T, alvo: T) ?usize {
    if (items.len == 0) return null;

    const ultimo_idx = items.len - 1;
    const ultimo_valor = items[ultimo_idx];

    // Verifica se o último já é o alvo
    if (ultimo_valor == alvo) return ultimo_idx;

    // Coloca o sentinela no final
    items[ultimo_idx] = alvo;

    // Busca sem verificar limite (sentinela garante parada)
    var i: usize = 0;
    while (items[i] != alvo) : (i += 1) {}

    // Restaura o valor original
    items[ultimo_idx] = ultimo_valor;

    // Se encontrou antes do sentinela, é válido
    if (i < ultimo_idx) return i;
    return null;
}

/// Busca linear que retorna todas as ocorrências.
pub fn buscaLinearTodas(
    comptime T: type,
    allocator: std.mem.Allocator,
    items: []const T,
    alvo: T,
) ![]usize {
    var resultados = std.ArrayList(usize).init(allocator);
    for (items, 0..) |item, i| {
        if (item == alvo) try resultados.append(i);
    }
    return resultados.toOwnedSlice();
}

/// Busca linear em structs por campo específico.
const Pessoa = struct {
    nome: []const u8,
    idade: u32,
};

pub fn buscaPorIdade(pessoas: []const Pessoa, idade: u32) ?usize {
    for (pessoas, 0..) |p, i| {
        if (p.idade == idade) return i;
    }
    return null;
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    const numeros = [_]i32{ 14, 33, 27, 10, 35, 19, 42, 44 };

    // Busca simples
    if (buscaLinear(i32, &numeros, 35)) |idx| {
        try stdout.print("35 encontrado na posição {d}\n", .{idx});
    }

    if (buscaLinear(i32, &numeros, 99)) |_| {
        try stdout.print("99 encontrado\n", .{});
    } else {
        try stdout.print("99 não encontrado\n", .{});
    }

    // Busca em structs
    const pessoas = [_]Pessoa{
        .{ .nome = "Ana", .idade = 25 },
        .{ .nome = "Bruno", .idade = 30 },
        .{ .nome = "Clara", .idade = 28 },
    };

    if (buscaPorIdade(&pessoas, 30)) |idx| {
        try stdout.print("Pessoa com 30 anos: {s}\n", .{pessoas[idx].nome});
    }
}

Análise de Complexidade

CasoTempoEspaço
Melhor casoO(1) — elemento no inícioO(1)
Caso médioO(n/2) = O(n)O(1)
Pior casoO(n) — elemento no final ou ausenteO(1)

Características

  • Simplicidade: o algoritmo mais fácil de implementar
  • Sem pré-requisitos: funciona em qualquer coleção, ordenada ou não
  • Espaço constante: O(1) de memória adicional
  • Versátil: funciona com qualquer tipo de dados

Quando Usar

  • Listas pequenas (n < 20): overhead de algoritmos complexos não compensa
  • Dados não ordenados: quando não é possível ordenar primeiro
  • Busca única: quando a busca é feita raramente
  • Listas encadeadas: acesso sequencial é a única opção

Para coleções ordenadas e buscas frequentes, use Busca Binária com O(log n).

Otimizações

  1. Sentinela: elimina a verificação de limite, melhorando constante
  2. Move-to-front: move o elemento encontrado para o início (melhora buscas repetidas)
  3. Transposição: troca o encontrado com o anterior (melhoria gradual)

Recursos Relacionados

Continue aprendendo Zig

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