Heap Binário em Zig — Implementação Completa

Heap Binário em Zig — Implementação Completa

O heap binário é uma árvore binária completa armazenada em array, onde cada pai tem valor menor (min-heap) ou maior (max-heap) que seus filhos. É a base para filas de prioridade e heap sort.

Conceito

Min-Heap (raiz = menor):
        1
       / \
      3   5
     / \ / \
    7  9 8  10

Armazenado como array:
  Índice:  0  1  2  3  4  5  6
  Valor:  [1, 3, 5, 7, 9, 8, 10]

Relações (índice baseado em 0):
  Pai de i:      (i - 1) / 2
  Filho esq de i: 2*i + 1
  Filho dir de i: 2*i + 2

Inserir 2:
  Adiciona no final: [1, 3, 5, 7, 9, 8, 10, 2]
  Sobe (bubble up): 2 < 7, troca → [1, 3, 5, 2, 9, 8, 10, 7]
                     2 < 3, troca → [1, 2, 5, 3, 9, 8, 10, 7]
                     2 > 1, para  → [1, 2, 5, 3, 9, 8, 10, 7]

Remover mínimo (1):
  Troca raiz com último: [7, 2, 5, 3, 9, 8, 10]
  Desce (bubble down):   7 > 2, troca → [2, 7, 5, 3, 9, 8, 10]
                          7 > 3, troca → [2, 3, 5, 7, 9, 8, 10]

Implementação em Zig

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

/// Heap binário genérico (min-heap ou max-heap via comparador).
pub fn HeapBinario(comptime T: type) type {
    return struct {
        const Self = @This();

        items: std.ArrayList(T),
        comparador: *const fn (T, T) bool,

        /// Cria um min-heap ou max-heap.
        pub fn init(allocator: Allocator, comparador: *const fn (T, T) bool) Self {
            return .{
                .items = std.ArrayList(T).init(allocator),
                .comparador = comparador,
            };
        }

        pub fn deinit(self: *Self) void {
            self.items.deinit();
        }

        /// Insere um elemento — O(log n).
        pub fn inserir(self: *Self, valor: T) !void {
            try self.items.append(valor);
            self.subirHeap(self.items.items.len - 1);
        }

        /// Remove e retorna o topo (mínimo ou máximo) — O(log n).
        pub fn removerTopo(self: *Self) ?T {
            if (self.items.items.len == 0) return null;
            const topo = self.items.items[0];
            const ultimo = self.items.items.len - 1;
            self.items.items[0] = self.items.items[ultimo];
            _ = self.items.pop();
            if (self.items.items.len > 0) self.descerHeap(0);
            return topo;
        }

        /// Consulta o topo sem remover — O(1).
        pub fn topo(self: *const Self) ?T {
            if (self.items.items.len == 0) return null;
            return self.items.items[0];
        }

        pub fn tamanho(self: *const Self) usize {
            return self.items.items.len;
        }

        pub fn vazio(self: *const Self) bool {
            return self.items.items.len == 0;
        }

        /// Constrói heap a partir de array existente — O(n).
        pub fn heapify(self: *Self, dados: []const T) !void {
            self.items.clearRetainingCapacity();
            try self.items.appendSlice(dados);
            // Bottom-up heapify
            var i: usize = self.items.items.len / 2;
            while (i > 0) {
                i -= 1;
                self.descerHeap(i);
            }
        }

        fn subirHeap(self: *Self, idx: usize) void {
            var i = idx;
            while (i > 0) {
                const pai = (i - 1) / 2;
                if (self.comparador(self.items.items[i], self.items.items[pai])) {
                    std.mem.swap(T, &self.items.items[i], &self.items.items[pai]);
                    i = pai;
                } else break;
            }
        }

        fn descerHeap(self: *Self, idx: usize) void {
            var i = idx;
            const n = self.items.items.len;
            while (true) {
                var melhor = i;
                const esq = 2 * i + 1;
                const dir = 2 * i + 2;
                if (esq < n and self.comparador(self.items.items[esq], self.items.items[melhor]))
                    melhor = esq;
                if (dir < n and self.comparador(self.items.items[dir], self.items.items[melhor]))
                    melhor = dir;
                if (melhor == i) break;
                std.mem.swap(T, &self.items.items[i], &self.items.items[melhor]);
                i = melhor;
            }
        }
    };
}

fn menorQue(a: i32, b: i32) bool {
    return a < b;
}

fn maiorQue(a: i32, b: i32) bool {
    return a > b;
}

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

    // Min-Heap
    var min_heap = HeapBinario(i32).init(allocator, &menorQue);
    defer min_heap.deinit();

    for ([_]i32{ 5, 3, 8, 1, 9, 2, 7 }) |v| {
        try min_heap.inserir(v);
    }

    try stdout.print("Min-Heap (ordem de saída): ", .{});
    while (min_heap.removerTopo()) |v| {
        try stdout.print("{d} ", .{v});
    }
    try stdout.print("\n", .{});

    // Max-Heap via heapify
    var max_heap = HeapBinario(i32).init(allocator, &maiorQue);
    defer max_heap.deinit();

    try max_heap.heapify(&[_]i32{ 5, 3, 8, 1, 9, 2, 7 });
    try stdout.print("Max-Heap (ordem de saída): ", .{});
    while (max_heap.removerTopo()) |v| {
        try stdout.print("{d} ", .{v});
    }
    try stdout.print("\n", .{});
}

Análise de Complexidade

OperaçãoTempoEspaço
InserirO(log n)O(1)
Remover topoO(log n)O(1)
Consultar topoO(1)O(1)
Heapify (build)O(n)O(1)

Recursos Relacionados

Continue aprendendo Zig

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