Árvore de Busca Binária (BST) em Zig — Implementação Completa

Árvore de Busca Binária (BST) em Zig — Implementação Completa

A BST (Binary Search Tree) é uma árvore binária onde, para cada nó, todos os valores na subárvore esquerda são menores e todos na direita são maiores. Isso permite busca, inserção e remoção em O(h), onde h é a altura.

Conceito

Inserindo: 8, 3, 10, 1, 6, 14, 4, 7, 13

            8
          /   \
         3     10
        / \      \
       1   6     14
          / \    /
         4   7  13

Propriedade BST:
  Para nó 3: esquerdo(1) < 3 < direito(6) ✓
  Para nó 8: esquerdo(3) < 8 < direito(10) ✓

Buscar 7:  8→3→6→7 ✓ (O(h))
Buscar 5:  8→3→6→4→null ✗

Remoção do nó 3 (dois filhos):
  Substitui pelo sucessor in-order (4):
            8
          /   \
         4     10
        / \      \
       1   6     14
            \    /
             7  13

Implementação em Zig

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

pub fn BST(comptime T: type) type {
    return struct {
        const Self = @This();

        pub const No = struct {
            valor: T,
            esquerdo: ?*No,
            direito: ?*No,
        };

        raiz: ?*No,
        tamanho: usize,
        allocator: Allocator,

        pub fn init(allocator: Allocator) Self {
            return .{ .raiz = null, .tamanho = 0, .allocator = allocator };
        }

        pub fn deinit(self: *Self) void {
            liberarSubarvore(self.allocator, self.raiz);
        }

        fn liberarSubarvore(allocator: Allocator, no: ?*No) void {
            if (no) |n| {
                liberarSubarvore(allocator, n.esquerdo);
                liberarSubarvore(allocator, n.direito);
                allocator.destroy(n);
            }
        }

        /// Insere um valor — O(h).
        pub fn inserir(self: *Self, valor: T) !void {
            self.raiz = try inserirNo(self.allocator, self.raiz, valor);
            self.tamanho += 1;
        }

        fn inserirNo(allocator: Allocator, no: ?*No, valor: T) !*No {
            if (no == null) {
                const novo = try allocator.create(No);
                novo.* = .{ .valor = valor, .esquerdo = null, .direito = null };
                return novo;
            }
            const n = no.?;
            if (valor < n.valor) {
                n.esquerdo = try inserirNo(allocator, n.esquerdo, valor);
            } else if (valor > n.valor) {
                n.direito = try inserirNo(allocator, n.direito, valor);
            }
            // valor == n.valor: duplicata, ignora
            return n;
        }

        /// Busca um valor — O(h).
        pub fn buscar(self: *const Self, valor: T) bool {
            return buscarNo(self.raiz, valor);
        }

        fn buscarNo(no: ?*const No, valor: T) bool {
            if (no == null) return false;
            const n = no.?;
            if (valor == n.valor) return true;
            if (valor < n.valor) return buscarNo(n.esquerdo, valor);
            return buscarNo(n.direito, valor);
        }

        /// Remove um valor — O(h).
        pub fn remover(self: *Self, valor: T) void {
            var removido = false;
            self.raiz = removerNo(self.allocator, self.raiz, valor, &removido);
            if (removido) self.tamanho -= 1;
        }

        fn removerNo(allocator: Allocator, no: ?*No, valor: T, removido: *bool) ?*No {
            if (no == null) return null;
            const n = no.?;

            if (valor < n.valor) {
                n.esquerdo = removerNo(allocator, n.esquerdo, valor, removido);
                return n;
            } else if (valor > n.valor) {
                n.direito = removerNo(allocator, n.direito, valor, removido);
                return n;
            }

            // Encontrou o nó para remover
            removido.* = true;

            // Caso 1: sem filhos ou um filho
            if (n.esquerdo == null) {
                const dir = n.direito;
                allocator.destroy(n);
                return dir;
            }
            if (n.direito == null) {
                const esq = n.esquerdo;
                allocator.destroy(n);
                return esq;
            }

            // Caso 2: dois filhos — encontra sucessor in-order
            const sucessor = minimoNo(n.direito.?);
            n.valor = sucessor.valor;
            n.direito = removerNo(allocator, n.direito, sucessor.valor, &(struct { v: bool }{ .v = false }).v);
            return n;
        }

        /// Valor mínimo — O(h).
        pub fn minimo(self: *const Self) ?T {
            if (self.raiz == null) return null;
            return minimoNo(self.raiz.?).valor;
        }

        fn minimoNo(no: *No) *No {
            var atual = no;
            while (atual.esquerdo) |esq| atual = esq;
            return atual;
        }

        /// Valor máximo — O(h).
        pub fn maximo(self: *const Self) ?T {
            if (self.raiz == null) return null;
            var atual = self.raiz.?;
            while (atual.direito) |dir| atual = dir;
            return atual.valor;
        }

        /// Travessia in-order (ordenada).
        pub fn inOrder(self: *const Self, allocator: Allocator) ![]T {
            var resultado = std.ArrayList(T).init(allocator);
            inOrderHelper(self.raiz, &resultado) catch {};
            return try resultado.toOwnedSlice();
        }

        fn inOrderHelper(no: ?*const No, resultado: *std.ArrayList(T)) !void {
            if (no == null) return;
            const n = no.?;
            try inOrderHelper(n.esquerdo, resultado);
            try resultado.append(n.valor);
            try inOrderHelper(n.direito, resultado);
        }
    };
}

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

    var bst = BST(i32).init(allocator);
    defer bst.deinit();

    for ([_]i32{ 8, 3, 10, 1, 6, 14, 4, 7, 13 }) |v| {
        try bst.inserir(v);
    }

    try stdout.print("Tamanho: {d}\n", .{bst.tamanho});
    try stdout.print("Mínimo: {?d}\n", .{bst.minimo()});
    try stdout.print("Máximo: {?d}\n", .{bst.maximo()});
    try stdout.print("Buscar 7: {}\n", .{bst.buscar(7)});
    try stdout.print("Buscar 5: {}\n", .{bst.buscar(5)});

    const ordenado = try bst.inOrder(allocator);
    defer allocator.free(ordenado);
    try stdout.print("In-order: ", .{});
    for (ordenado) |v| try stdout.print("{d} ", .{v});
    try stdout.print("\n", .{});

    // Remoção
    bst.remover(3);
    try stdout.print("Após remover 3: ", .{});
    const apos = try bst.inOrder(allocator);
    defer allocator.free(apos);
    for (apos) |v| try stdout.print("{d} ", .{v});
    try stdout.print("\n", .{});
}

Análise de Complexidade

OperaçãoMelhor/MédioPior (desbalanceada)
BuscaO(log n)O(n)
InserçãoO(log n)O(n)
RemoçãoO(log n)O(n)
Min/MaxO(log n)O(n)

Recursos Relacionados

Continue aprendendo Zig

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