Á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ção | Melhor/Médio | Pior (desbalanceada) |
|---|---|---|
| Busca | O(log n) | O(n) |
| Inserção | O(log n) | O(n) |
| Remoção | O(log n) | O(n) |
| Min/Max | O(log n) | O(n) |
Recursos Relacionados
- Árvore Binária — Estrutura base
- Árvore AVL — BST auto-balanceada
- Red-Black Tree — BST com coloração
- Busca Binária — Conceito similar em arrays