Aula 18 - Algoritmos de Busca Avançada (Binária e Hashing) 🔍
Objetivo Pedagógico
Objetivo: Implementação e análise matemática de busca binária iterativa e recursiva, tratamento de colisões em tabelas Hash (Sondagem Linear vs Encadeamento) e funções de dispersão.
📑 1. Fundamentos Teóricos & Análise Técnica
A recuperação rápida de informações é o alicerce dos bancos de dados e sistemas de busca modernos. Quando uma coleção está desordenada, a única alternativa determinística é a Busca Linear \(O(n)\). Contudo, ao impor estruturação prévia, a latência de consulta é comprimida:
- Busca Binária (\(O(\log n)\)): Exige que a coleção esteja previamente ordenada. Compara o elemento do meio (mid): se o alvo for menor, descarta a metade superior; se for maior, descarta a metade inferior.
- Tabelas Hash / Tabelas de Dispersão (\(O(1)\) Médio): Mapeiam chaves arbitrárias para índices de vetores através de uma Função de Hash determinística.
- Resolução de Colisões: Pelo Princípio da Casa dos Pombos, quando duas chaves diferentes produzem o mesmo índice de hash:
- Encadeamento Separado (Chaining): Cada posição do vetor armazena uma lista encadeada contendo todos os elementos que colidiram naquele slot.
- Endereçamento Aberto (Open Addressing): Procura o próximo slot livre no vetor via sondagem linear (Linear Probing) ou sondagem quadrática.
📐 Arquitetura Conceitual & Diagrama de Fluxo
flowchart TD
Key["Chave de Entrada: 'usuario_123'"] --> HashFn["Função Hash (MurmurHash / DJB2)"]
HashFn --> Index["Índice Calculado: 4"]
Index --> Table["Tabela Hash (Vetor de Buckets)"]
Table --> Slot4{"Slot 4 Ocupado?"}
Slot4 -->|Não: Livre| Store["Grava Registro Diretamente em O(1)"]
Slot4 -->|Sim: Colisão| Chain["Encadeamento em Lista / Sondagem Aberta"]
style Key fill:#e1f5fe,stroke:#01579b
style HashFn fill:#fff3e0,stroke:#e65100
style Store fill:#e8f5e9,stroke:#2e7d32
style Chain fill:#ffebee,stroke:#c62828 🔍 Pilares e Diretrizes Técnicas
Nesta unidade, aprofundamos os seguintes conceitos fundamentais: - Cálculo do Ponto Médio sem Overflow: Uso de mid = low + (high - low) / 2 para prevenir estouro de inteiros em arrays gigantes. - Fator de Carga (Load Factor \(\alpha = N/M\)): Proporção de elementos ocupados. Quando \(\alpha > 0.7\), a tabela deve sofrer Rehash dobrando de tamanho. - Uniformidade da Função de Hash: Distribuição uniforme de chaves para evitar clusters primários que degenerem a tabela para \(O(n)\). - Resistência a Ataques HashDoS: Uso de sementes aleatórias para evitar que atacantes explorem colisões deliberadas.
🛠️ 2. Implementação Prática em Estruturas de Dados e Algoritmos de Busca
Abaixo está a implementação técnica de referência, estruturada com padrões de engenharia de software e foco em robustez:
// hash_table_chaining.c (Tabela Hash com Resolução por Encadeamento em C)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TABLE_SIZE 10
typedef struct HashNode {
char key[64];
int value;
struct HashNode* next;
} HashNode;
typedef struct {
HashNode* buckets[TABLE_SIZE];
} HashTable;
// Função Hash clássica djb2
unsigned long hash_djb2(const char* str) {
unsigned long hash = 5381;
int c;
while ((c = *str++)) hash = ((hash << 5) + hash) + c;
return hash % TABLE_SIZE;
}
void hash_insert(HashTable* table, const char* key, int value) {
unsigned long idx = hash_djb2(key);
HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
strcpy(newNode->key, key);
newNode->value = value;
// Insere no início da lista encadeada (O(1))
newNode->next = table->buckets[idx];
table->buckets[idx] = newNode;
}
💡 Análise Passo a Passo do Código
- Função djb2: Algoritmo de dispersão rápido que espalha bits de strings com deslocamentos (
hash << 5) e somas. - Encadeamento em O(1): Novas colisões são inseridas na cabeça da lista encadeada no bucket correspondente sem percorrer a lista inteira.
- Acesso Instantâneo: Acesso médio em tempo constante \(O(1)\) para recuperação posterior.
🎯 3. Próximos Passos & Sequência Didática
-
Slides da Aula
-
Quiz de Fixação
-
Exercícios Práticos
-
Desafio de Projeto