Tópicos Avançados de Algoritmos
1. Geometria Computacional: O Poder do Produto Vetorial
Em Geometria Computacional, evitamos ao máximo usar ângulos, trigonometria (sin, cos, atan2) ou divisão, pois a imprecisão do double acumula. A ferramenta suprema é o Produto Vetorial (Cross Product).
Dados dois vetores \(\vec{A}\) e \(\vec{B}\), o Cross Product em 2D é \(A_x B_y - A_y B_x\). - Positivo: \(\vec{B}\) está à esquerda de \(\vec{A}\) (Sentido Anti-horário). - Negativo: \(\vec{B}\) está à direita de \(\vec{A}\) (Sentido Horário). - Zero: Colineares.
Aplicações
- Área de Polígono: A área é \(\frac{1}{2} | \sum (x_i y_{i+1} - x_{i+1} y_i) |\).
- Interseção de Segmentos: Se os extremos de um segmento estão em lados opostos da reta do outro segmento (testado com Cross Product), e vice-versa, eles cruzam.
- Convex Hull (Fecho Convexo): Encontrar o menor polígono convexo que engloba um conjunto de pontos (como um elástico). Algoritmo Monotone Chain (\(O(N \log N)\)).
2. Strings e Dicionários
Trie (Árvore de Prefixos)
Estrutura para armazenar um dicionário de palavras. Cada aresta é uma letra. Muito usado em Autocomplete e Corretores Ortográficos.
class TrieNode {
TrieNode[] children = new TrieNode[26];
boolean isEndOfWord;
}
public void insert(String word) {
TrieNode curr = root;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (curr.children[idx] == null) {
curr.children[idx] = new TrieNode();
}
curr = curr.children[idx];
}
curr.isEndOfWord = true;
}KMP (Knuth-Morris-Pratt)
Imagine buscar a palavra “ANA” dentro de “BANANA”. O algoritmo ingênuo é \(O(N \cdot M)\). O KMP pré-processa o padrão para saber “quanto posso pular” se falhar. Cria o array LPS (Longest Prefix Suffix). Complexidade: \(O(N + M)\).
3. LCA (Lowest Common Ancestor)
Dado uma árvore, quem é o ancestral comum mais próximo entre \(u\) e \(v\)? Exemplo: Em Biologia (Árvore Filogenética), o LCA de Humano e Chimpanzé é um primata ancestral comum.
Solução Lenta: Subir os pais de um até a raiz, marcar o caminho, subir o outro até achar um marcado. \(O(N)\) por consulta. Lento.
Solução Rápida (Binary Lifting): Pré-processamos uma tabela up[u][i] que guarda o \(2^i\)-ésimo ancestral de \(u\). Isso permite “saltar” potências de 2 na árvore (\(1, 2, 4, 8 \dots\)). - Pré-processamento: \(O(N \log N)\). - Consulta LCA: \(O(\log N)\).
// up[u][i] = 2^i-th ancestor of u
void preprocess(int root) {
// DFS para calcular up[u][0] (pai direto) e profundidade...
// DP para calcular as potências
for (int i = 1; i <= MAX_LOG; i++) {
for (int u = 0; u < N; u++) {
if (up[u][i-1] != -1) {
up[u][i] = up[up[u][i-1]][i-1];
} else {
up[u][i] = -1;
}
}
}
}
int lca(int u, int v) {
if (depth[u] < depth[v]) return lca(v, u); // u deve ser mais fundo
// 1. Sobe u para o mesmo nível de v
for (int i = MAX_LOG; i >= 0; i--) {
if (depth[u] - (1 << i) >= depth[v]) {
u = up[u][i];
}
}
if (u == v) return u;
// 2. Sobem juntos até logo abaixo do LCA
for (int i = MAX_LOG; i >= 0; i--) {
if (up[u][i] != up[v][i]) {
u = up[u][i];
v = up[v][i];
}
}
return up[u][0]; // Pai do ponto de parada é o LCA
}Isso é vital para calcular distância entre nós em árvore: \(Dist(u, v) = Depth(u) + Depth(v) - 2 \cdot Depth(LCA(u, v))\).