← Cursos

Plano de aulas da disciplina de Estruturas de Dados e Algoritmos (ADS, INF006). São 36 encontros em quatro blocos: 32 aulas e 4 apresentações de projeto (aulas 9, 18, 27 e 36). Fora dos 36 encontros há um anexo, Base Prática em C (módulos B1 a B5, para estudar antes do bloco 1), e um bloco complementar opcional que aprofunda a ordenação (C1 a C5).

Como é cada aula: um conjunto de slides em quatro partes.
  1. Teoria, a partir de Cormen et al., Algoritmos: teoria e prática (3ª edição), com as figuras do livro e o pseudocódigo em português.
  2. Prática em C: implementação comentada, testada, para baixar e rodar.
  3. Experimento: uma hipótese tirada da teoria, um programa em C que mede, e o gráfico do medido contra a curva teórica. Rode na sua máquina: gcc -O2 arquivo.c -o exp -lm && ./exp.
  4. Conexões: onde o conceito aparece em outras áreas da computação.
As figuras dos slides são reproduzidas de Cormen, Leiserson, Rivest e Stein (MIT Press, 2009) para fins didáticos, com a indicação da figura original.

Anexo Base Prática em C

Cinco módulos de apoio, antes do bloco 1, com o C que as estruturas de dados exigem além do básico: memória e alocação dinâmica, ponteiros (simples, duplos, para funções e para structs), structs, funções genéricas e arquivos.

B1 Memória: pilha, heap e alocação dinâmica AnexoKernighan e Ritchie, C: a linguagem de programação, seções 5.4 e 8.7; Cormen et al., seção 10.3

Objetivos

  • Distinguir a pilha (variáveis locais) do heap (alocação dinâmica)
  • Usar malloc, calloc, realloc e free corretamente
  • Reconhecer vazamentos, ponteiros soltos e liberações duplas
  • Medir quanto um malloc pequeno custa em bytes e em tempo

Experimento

Quantos bytes de heap e quantos nanossegundos custa cada malloc de 16 bytes?

Cada malloc de 16 bytes ocupa 32 bytes e custa dezenas de nanossegundos. Uma lista ligada de inteiros gasta muito mais memória e tempo que um vetor com os mesmos dados. É por isso que estruturas de alto desempenho alocam em blocos (pools) e por isso a aula 16 mostra listas guardadas em vetores.

Conexões

  • Sistemas operacionais: malloc pede páginas ao kernel com brk e mmap; a memória virtual e a paginação ficam por trás de cada ponteiro.
  • Segurança: Estouros de buffer e uso após free são as falhas mais exploradas da história; linguagens como Rust impedem esses erros já na compilação.
  • Linguagens com coleta de lixo: Java, Python e Go liberam a memória sozinhas (coletor de lixo); C dá controle total, e a responsabilidade junto.
  • Sistemas embarcados: Em microcontroladores com poucos KB, o heap fragmenta: por isso o padrão MISRA C proíbe malloc depois da inicialização.
B2 Ponteiros: endereços, aritmética e ponteiro para ponteiro AnexoKernighan e Ritchie, C: a linguagem de programação, capítulo 5; Cormen et al., seção 10.3

Objetivos

  • Usar & e * e entender o que um ponteiro guarda
  • Relacionar vetores e ponteiros pela aritmética de ponteiros
  • Passar parâmetros por referência e alterar um ponteiro dentro de uma função (int **)
  • Alocar matrizes dinâmicas e medir o efeito da ordem de percurso

Experimento

Percorrer uma matriz por linhas e por colunas custa o mesmo tempo por elemento?

A mesma conta, na ordem errada, fica 30 vezes mais lenta para \(n = 4096\). Ponteiros dão acesso direto à memória, e a memória tem geografia: vizinhos são baratos, saltos são caros. Nas próximas aulas, a mesma lição aparece em listas ligadas (aula 14) e em grafos (aula 29).

Conexões

  • Sistemas operacionais: O kernel manipula páginas, processos e dispositivos por ponteiros; um ponteiro inválido no kernel derruba a máquina inteira.
  • Compiladores: Otimizadores trocam a ordem de laços (loop interchange) para percorrer matrizes por linhas; a análise de aliasing decide se dois ponteiros podem se sobrepor.
  • Hardware e GPUs: Bibliotecas de álgebra linear (BLAS) e GPUs dividem matrizes em blocos que cabem no cache: o mesmo efeito do experimento, explorado ao máximo.
  • Bancos de dados: Bancos orientados a linhas (OLTP) e a colunas (OLAP, como DuckDB) são as duas ordens do experimento: cada um vence num tipo de consulta.
B3 Structs: registros, ponteiros para structs e nós AnexoKernighan e Ritchie, C: a linguagem de programação, capítulo 6; Cormen et al., seção 10.3

Objetivos

  • Declarar structs com typedef e acessar campos com . e ->
  • Construir vetores de structs e structs autorreferentes (nós)
  • Entender alinhamento e preenchimento (sizeof, offsetof)
  • Medir vetor de structs (AoS) contra struct de vetores (SoA)

Experimento

Somar um único campo de n registros custa o mesmo num vetor de structs (AoS) e num vetor só com aquele campo (SoA)?

A organização dos dados na memória muda o desempenho em 5 vezes, com o mesmo algoritmo. Agrupar em structs é o natural para programar; separar em vetores é o natural para o hardware. Bancos colunares, motores de jogos e bibliotecas de ciência de dados escolhem SoA pelo mesmo motivo.

Conexões

  • Redes: Cabeçalhos IP, TCP e Ethernet são structs com campos em posições fixas; o preenchimento errado corrompe pacotes (__attribute__((packed))).
  • Sistemas de arquivos: O inode do Linux e as entradas de diretório são structs gravadas no disco byte a byte.
  • Motores de jogos: Arquiteturas entity component system guardam cada componente (posição, velocidade) num vetor próprio: SoA para atualizar milhões de objetos por quadro.
  • Ciência de dados: NumPy, pandas e Apache Arrow guardam cada coluna num vetor contíguo: é o SoA do experimento, e a razão de serem tão rápidos.
B4 Funções: recursão, ponteiros para funções e código genérico AnexoKernighan e Ritchie, C: a linguagem de programação, seções 4.10, 5.11 e B.5

Objetivos

  • Entender a pilha de chamadas e o custo de memória da recursão
  • Declarar e usar ponteiros para funções
  • Escrever código genérico com void * e qsort
  • Medir os bytes de pilha por chamada e as comparações do qsort

Experimento

Quantos bytes de pilha cada chamada recursiva usa? Quantas vezes o qsort chama o comparador?

A pilha cresce exatamente em linha reta com a profundidade (um quadro de tamanho fixo por chamada), e o qsort chama o comparador \(n\lg n - 1{,}26\,n\) vezes: a fórmula do merge sort. Medir revela o que a biblioteca faz por dentro: o nome diz “quick”, mas o comportamento é de merge sort.

Conexões

  • Sistemas operacionais: Tratadores de sinais e de interrupções, pthread_create e os drivers de dispositivo (struct file_operations) são tabelas de ponteiros para funções.
  • Interfaces gráficas: Botões e eventos chamam funções registradas (callbacks): GTK, o DOM do navegador e o Android funcionam assim.
  • Programação orientada a objetos: Métodos virtuais do C++ são ponteiros para funções numa tabela (vtable); o mesmo padrão aparece no kernel escrito em C.
  • Inteligência artificial: Funções de ativação, de perda e otimizadores são passados como parâmetros: um treinador genérico recebe “qual função usar.
B5 Arquivos, argumentos e erros AnexoKernighan e Ritchie, C: a linguagem de programação, capítulo 7 e seção 5.10

Objetivos

  • Ler e escrever arquivos texto e binários
  • Receber parâmetros pela linha de comando (argc, argv)
  • Tratar erros com valores de retorno, perror e assert
  • Medir o tamanho e o tempo de arquivos texto contra binários

Experimento

Quantos bytes e quantos nanossegundos custa cada número gravado e relido em texto e em binário?

Os dois formatos são lineares, mas com constantes muito diferentes: o texto é 72% maior e 35 vezes mais lento. O binário é exatamente \(4n\) bytes. Por isso bancos de dados, imagens e modelos de IA são guardados em binário, e dados para trocar com pessoas e outras ferramentas, em texto.

Conexões

  • Bancos de dados: Tabelas e índices ficam em arquivos binários de páginas de tamanho fixo (4 a 16 KB); fseek para a página \(k\) é k * tamanho.
  • Redes: Protocolos binários (TCP/IP, gRPC/Protobuf) são compactos e rápidos; protocolos de texto (HTTP/1.1, JSON) são legíveis. A escolha é a mesma do experimento.
  • Ciência de dados: CSV é universal, mas Parquet e Arrow (binários e colunares) são até dezenas de vezes menores e mais rápidos para grandes volumes.
  • Ferramentas de linha de comando: grep, sort e gcc leem argv, leem stdin, escrevem stdout e devolvem um código de saída: é o que permite encadeá-los com |.

Bloco 1 Fundamentos, ordenação e custo

Análise de algoritmos, ordenação por inserção, merge sort, notação assintótica, recorrências, dividir e conquistar e heaps. Termina com o Projeto 1.

1 Algoritmos, eficiência e o método experimental Cormen, capítulo 1

Objetivos

  • Definir algoritmo, instância e corretude
  • Explicar por que eficiência importa mais que hardware
  • Medir tempo de execução em C de forma confiável
  • Usar a razão de dobra para reconhecer crescimento linear e quadrático

Experimento

Ao dobrar n, o tempo de um algoritmo linear dobra e o de um quadrático quadruplica?

Os dois tempos seguem a forma prevista: a curva do quadrático é uma parábola e a do linear, uma reta. Para \(n = 32.000\), o algoritmo quadrático já gasta cerca de 100 vezes mais que o linear, e a distância cresce com \(n\). A forma da curva é uma propriedade do algoritmo; a constante é da máquina.

Conexões

  • Inteligência artificial: Treinar um modelo de linguagem custa da ordem de \(6 \times\) parâmetros \(\times\) tokens operações: a análise de custo decide quais modelos são viáveis.
  • Criptografia: A segurança do RSA depende de não existir algoritmo eficiente conhecido para fatorar inteiros grandes: a ineficiência vira proteção.
  • Computação verde: Data centers consomem cerca de 1–2% da energia elétrica mundial. Trocar \(n^2\) por \(n\lg n\) economiza energia, não só tempo.
  • Engenharia de desempenho: Ferramentas como perf, gprof e testes de regressão de desempenho em integração contínua aplicam o método desta aula na indústria.
2 Ordenação por inserção e invariantes de laço Cormen, seção 2.1

Objetivos

  • Executar a ordenação por inserção à mão, passo a passo
  • Provar a corretude com um invariante de laço: inicialização, manutenção e término
  • Traduzir o pseudocódigo (índices \(1..n\)) para C (índices \(0..n-1\))
  • Medir comparações e confrontar com \(n-1\), \(n^2/4\) e \(n(n-1)/2\)

Experimento

Quantas comparações a ordenação por inserção faz em entradas ordenadas, aleatórias e invertidas?

As três previsões se confirmam: exatamente \(n-1\) e \(n(n-1)/2\) nos casos extremos, e \(n^2/4\) com desvio abaixo de 3% no caso aleatório. A ordenação por inserção é linear em dados ordenados e quadrática em dados aleatórios: o tipo de entrada importa tanto quanto o tamanho.

Conexões

  • Bibliotecas de linguagens: O Timsort (sorted do Python, Arrays.sort de objetos no Java) ordena trechos curtos com inserção binária: para \(n\) pequeno, ela vence algoritmos \(n\lg n\).
  • Sistemas embarcados: No lugar, sem recursão e com código minúsculo: cabe em microcontroladores com poucos KB de RAM, onde malloc e pilha profunda são luxo.
  • Fluxos de dados: É um algoritmo online: ordena à medida que os dados chegam. Manter um placar ao vivo ou as \(k\) maiores leituras de um sensor é uma inserção por evento.
  • Verificação formal: Invariantes de laço são a base de ferramentas como Dafny, Frama-C (ACSL) e Why3, que provam código real. Sem o invariante, o provador não consegue.
3 Análise de algoritmos: o modelo RAM e os casos Cormen, seção 2.2

Objetivos

  • Descrever o modelo RAM e o que ele ignora
  • Calcular \(T(n)\) da inserção somando custo \(\times\) vezes de cada linha
  • Distinguir melhor caso, pior caso e caso médio
  • Prever e medir o caso médio exato: \(n(n-1)/4 + n - H_n\)

Experimento

A média de comparações da inserção em permutações aleatórias bate com n(n−1)/4 + n − H_n?

A fórmula do caso médio é confirmada: a média medida fica a menos de 1% de \(n(n-1)/4 + n - H_n\), praticamente a metade do pior caso. O histograma mostra que execuções muito diferentes da média são raras. Caso médio quadrático: para dados aleatórios, a inserção não escapa de \(\Theta(n^2)\).

Conexões

  • Sistemas de tempo real: Freio ABS e marca-passo não podem atrasar: a análise de pior caso de tempo de execução (WCET) garante que todo prazo seja cumprido.
  • Segurança: Ataques de complexidade enviam a entrada do pior caso de propósito: expressões regulares catastróficas (ReDoS) e colisões em tabelas hash derrubam servidores.
  • Compiladores: Otimizadores usam modelos de custo por instrução, como a tabela desta aula, para decidir desenrolar laços, embutir funções ou vetorizar.
  • Serviços web: Média engana: plataformas monitoram a cauda (latência p99, p99,9), porque com milhões de requisições o pior caso acontece a cada segundo.
4 Dividir e conquistar: merge sort Cormen, seção 2.3

Objetivos

  • Aplicar o paradigma dividir, conquistar e combinar
  • Executar Merge e provar sua corretude por invariante
  • Escrever a recorrência do merge sort e resolvê-la pela árvore de recursão
  • Medir \(n\lg n\) e achar o ponto em que a inserção deixa de valer a pena

Experimento

O merge sort faz cerca de n lg n comparações? Para que n ele passa a vencer a inserção?

O merge sort faz \(n\lg n - 1{,}26\,n\) comparações, com desvio menor que 1%. Mas a ordem de crescimento não diz tudo: para \(n\) até algumas centenas, a inserção é mais rápida. Algoritmos híbridos (Timsort, introsort) usam inserção abaixo de um limiar e dividir e conquistar acima dele.

Conexões

  • Bancos de dados: O sort-merge join ordena duas tabelas pela chave e as intercala. A ordenação externa ordena arquivos maiores que a RAM em blocos e depois os intercala.
  • Armazenamento (LSM-trees): RocksDB, Cassandra e LevelDB escrevem em blocos ordenados e os compactam por intercalação: o Merge em escala de terabytes.
  • Big Data: MapReduce e Spark dividem os dados entre máquinas, processam em paralelo e combinam os resultados: o mesmo paradigma, num cluster.
  • Computação paralela: As duas metades são independentes: o merge sort paraleliza naturalmente em CPUs com vários núcleos e em GPUs.
5 Notação assintótica: \(O\), \(\Omega\) e \(\Theta\) Cormen, capítulo 3

Objetivos

  • Definir \(\Theta\), \(O\), \(\Omega\), \(o\) e \(\omega\) e interpretá-las graficamente
  • Provar relações assintóticas exibindo constantes \(c\) e \(n_0\)
  • Ordenar funções comuns pela taxa de crescimento
  • Estimar a ordem de crescimento a partir de medições (log-log e razão de dobra)

Experimento

Medindo operações de quatro algoritmos, conseguimos identificar lg n, n, n lg n e n²?

As contagens batem exatamente com \(n(n-1)/2\), \(n\lg n\), \(n\) e \(\lg n\). Em escala log-log, a ordem de crescimento aparece como inclinação. Para estimar \(k\) a partir de duas medições: \(k \approx \lg\big(T(2n)/T(n)\big)\), a razão de dobra da aula 1.

Conexões

  • Ciência de dados: O \(k\)-means custa \(O(n\,k\,d\,i)\); um algoritmo \(O(n^2)\) inviabiliza bases com milhões de linhas. A notação assintótica decide o que escala.
  • Teoria da complexidade: P contra NP pergunta se problemas verificáveis em tempo polinomial também são resolvíveis em tempo polinomial: um dos sete problemas do milênio.
  • Bioinformática: Alinhar duas sequências custa \(\Theta(mn)\): para genomas de bilhões de bases, isso força heurísticas como BLAST.
  • Contratos de serviço (SLA): Garantir resposta em 100 ms para qualquer carga exige saber como o tempo cresce com os dados; a ordem de crescimento entra no planejamento de capacidade.
6 Recorrências: árvore de recursão e teorema mestre Cormen, seções 4.3 a 4.5

Objetivos

  • Modelar algoritmos recursivos por recorrências
  • Resolver recorrências pela árvore de recursão e confirmar por substituição
  • Aplicar os três casos do teorema mestre
  • Conferir numericamente as previsões do teorema

Experimento

Os valores exatos de quatro recorrências seguem as soluções do teorema mestre?

As soluções exatas coincidem com os valores calculados, e a de Strassen converge para \(\frac73\,n^{\lg 7}\). O teorema mestre acerta a ordem de crescimento; para a constante, é preciso resolver a recorrência com mais cuidado (substituição ou árvore completa).

Conexões

  • Processamento de sinais: A FFT satisfaz \(T(n) = 2T(n/2) + \Theta(n)\): \(n\lg n\) em vez de \(n^2\) viabiliza áudio digital, 4G/5G, ressonância magnética e compressão JPEG/MP3.
  • Álgebra linear e IA: Strassen (\(n^{2,81}\)) e sucessores mostram que multiplicar matrizes, o núcleo das redes neurais, pode custar menos que \(n^3\).
  • Computação paralela: Algoritmos paralelos são analisados por duas recorrências: trabalho total e span (caminho crítico). O paralelismo é a razão entre elas.
  • Linguagens e sistemas: A profundidade da recursão é a altura da árvore: com \(\lg n\) níveis, a pilha de chamadas fica pequena; com \(n\) níveis, ocorre estouro de pilha.
7 Subarranjo máximo: três algoritmos, três ordens Cormen, seção 4.1

Objetivos

  • Reduzir o problema de compra e venda de ações ao subarranjo máximo
  • Resolver por força bruta, por divisão e conquista e pelo algoritmo linear
  • Analisar o algoritmo de divisão e conquista pela recorrência \(2T(n/2) + \Theta(n)\)
  • Medir \(n^2/2\), \(n\lg n\) e \(n\) operações

Experimento

Quantas somas fazem a força bruta, a divisão e conquista e o algoritmo de Kadane?

As contagens são exatamente \(n(n+1)/2\), \(n\lg n\) e \(n\), e as três respostas coincidem em todos os testes. Divisão e conquista melhora muito a força bruta, mas uma ideia melhor (programação dinâmica, aula 33) chega a \(\Theta(n)\): nenhum paradigma é a última palavra.

Conexões

  • Finanças: Melhor janela de compra e venda, maior sequência de lucro ou de perda (drawdown) numa carteira: o problema desta aula com dados reais.
  • Bioinformática: Atribuindo pesos a aminoácidos ou nucleotídeos, o segmento de soma máxima localiza regiões transmembranares em proteínas e ilhas CpG no DNA.
  • Visão computacional: A versão 2D, o retângulo de soma máxima, encontra a região mais clara ou mais “ativa de uma imagem em \(O(n^3)\), com Kadane nas colunas.
  • Monitoramento: Em séries temporais (CPU, batimentos, tráfego), subtrair a média e achar o subarranjo máximo localiza a janela de maior anomalia.
8 Heaps e heapsort Cormen, capítulo 6 (seções 6.1 a 6.4)

Objetivos

  • Representar uma árvore binária quase completa em um vetor
  • Manter a propriedade de heap com Max-Heapify
  • Mostrar que Build-Max-Heap é \(O(n)\), não \(O(n\lg n)\)
  • Medir comparações: \(\approx 2n\) na construção, \(\approx 2n\lg n\) no heapsort

Experimento

Construir um heap faz O(n) comparações e o heapsort faz cerca de 2 n lg n?

A construção é linear (cerca de \(1{,}9\,n\) comparações), confirmando que a análise ingênua \(O(n\lg n)\) era pessimista. O heapsort completo fica perto de \(1{,}8\,n\lg n\): o dobro de comparações do merge sort, mas no lugar, sem memória extra.

Conexões

  • Sistemas operacionais: Temporizadores do kernel, filas de prioridade de processos e o temporizador do runtime de Go usam heaps para achar o próximo evento em \(O(\lg n)\).
  • Simulação de redes: Simuladores de eventos discretos (ns-3, OMNeT++) mantêm uma fila de eventos ordenada por tempo: extrair o próximo é Extract-Min.
  • Sistemas embarcados: Heapsort garante \(O(n\lg n)\) sem memória extra nem recursão profunda; o std::sort do C++ (introsort) recorre a ele quando o quicksort degenera.
  • Jogos e mapas: Dijkstra e A*, usados em GPS e na movimentação de personagens, dependem de uma fila de prioridade em heap (aulas 17 e 31).
9 Projeto 1: laboratório de ordenação Projeto

Apresentação dos projetos do Bloco 1. Cada equipe implementa, instrumenta e compara algoritmos de ordenação, e confronta as medições com a teoria.

Entregas

  • Implementação em C de inserção, merge sort, heapsort e um híbrido (inserção abaixo de um limiar \(k\))
  • Experimento com \(n\) até \(10^6\) em quatro tipos de entrada: aleatória, ordenada, invertida e quase ordenada (1% fora do lugar)
  • Gráficos de comparações e de tempo com as curvas teóricas ajustadas e a razão medido/teórico
  • O melhor limiar \(k\) do híbrido na máquina da equipe, com justificativa
  • Apresentação de 10 minutos: hipótese, método, resultados, o que não bateu com a teoria e por quê

Bloco 2 Dicionários e estruturas lineares

Dicionários, pilhas, filas, vetores dinâmicos e listas: simplesmente e duplamente ligadas, circulares, com sentinela e em vetores; filas de prioridade. Termina com o Projeto 2.

10 Dicionários e conjuntos dinâmicos Cormen, introdução da parte III e seção 10.1

Objetivos

  • Definir conjunto dinâmico e as operações de dicionário
  • Separar consultas (busca, mínimo, máximo, sucessor) de modificações (inserção, remoção)
  • Implementar um dicionário em vetor ordenado com busca binária
  • Medir o custo de inserir e de buscar em vetor ordenado e desordenado

Experimento

Quanto custam n inserções e n buscas num dicionário em vetor ordenado e em vetor desordenado?

As três previsões se confirmam: \(n^2/4\) deslocamentos e \(n\lg n\) comparações no vetor ordenado, \(n(n+1)/2\) comparações no desordenado. Vetores obrigam a escolher entre inserir rápido e buscar rápido. O resto do curso mostra estruturas que fazem as duas coisas em \(O(1)\) ou \(O(\lg n)\).

Conexões

  • Bancos de dados: Uma tabela é um conjunto dinâmico de registros; índices são dicionários sobre uma coluna, e o otimizador escolhe entre varrer e buscar conforme o custo.
  • Linguagens: dict e set do Python, Map do JavaScript, std::map e std::unordered_map do C++: o mesmo tipo abstrato com implementações diferentes.
  • Compiladores: A tabela de símbolos é um dicionário de identificadores; escopos aninhados viram uma pilha de dicionários (aula 11).
  • Redes: Tabelas ARP, de rotas e de conexões são dicionários consultados milhões de vezes por segundo: a implementação define a latência.
11 Pilhas Cormen, seção 10.1

Objetivos

  • Implementar pilhas em vetor (com transbordamento) e em lista ligada
  • Analisar Push e Pop em \(O(1)\)
  • Avaliar expressões pós-fixas com uma pilha
  • Medir o custo por operação das duas implementações

Experimento

Quanto custa, em nanossegundos, cada operação de pilha em vetor e em lista ligada?

As duas implementações têm custo constante por operação, como prevê a análise, mas a lista ligada é cerca de 25 vezes mais cara: malloc, free e saltos pela memória. A notação \(O\) esconde constantes; quando a capacidade é previsível, o vetor (ou o vetor dinâmico da aula 13) é a melhor pilha.

Conexões

  • Compiladores: Analisadores sintáticos casam parênteses e blocos com pilhas; a máquina virtual da JVM e a do Python são máquinas de pilha: iadd desempilha dois e empilha a soma.
  • Navegadores e editores: Voltar e avançar, desfazer e refazer: duas pilhas. Calculadoras HP usavam notação pós-fixa por ser mais rápida de digitar e de avaliar.
  • Sistemas operacionais: Cada thread tem sua pilha de chamadas; o estouro dela (stack overflow) é o transbordamento desta aula, em hardware.
  • Inteligência artificial: Busca em profundidade com backtracking (labirintos, sudoku, provadores de teoremas) guarda as alternativas pendentes numa pilha (aula 29).
12 Filas Cormen, seção 10.1

Objetivos

  • Implementar filas em vetor circular e em lista ligada
  • Distinguir fila cheia de fila vazia
  • Conhecer deques (filas de duas pontas)
  • Simular uma fila de atendimento e conferir com a teoria das filas

Experimento

Na simulação de uma fila M/M/1, o tempo médio de espera segue ρ/(1−ρ)?

A simulação reproduz \(\rho/(1-\rho)\) com erro de poucos por cento. A fila FIFO, uma estrutura simples, é o coração de toda simulação de atendimento. A lição prática: um sistema com 85% de ocupação já tem esperas longas; perto de 100%, elas explodem.

Conexões

  • Sistemas operacionais: Processos prontos esperam numa fila; o escalonamento round robin é uma fila circular. Buffers de teclado, disco e impressão são filas.
  • Redes: Roteadores guardam pacotes em filas de saída; a teoria das filas desta aula explica o atraso e a perda quando o enlace fica congestionado.
  • Computação em nuvem: Filas de mensagens (RabbitMQ, Kafka, SQS) desacoplam serviços: quem produz enfileira, quem consome desenfileira no seu ritmo.
  • Algoritmos de grafos: A busca em largura (aula 28) explora um grafo camada por camada usando exatamente uma fila FIFO.
13 Vetores dinâmicos e análise amortizada Cormen, seções 17.1 e 17.4

Objetivos

  • Fazer um vetor crescer e encolher conforme o uso
  • Calcular o custo amortizado pelo método agregado
  • Entender por que encolher só com \(1/4\) de ocupação
  • Medir \(2n\) cópias contra \(n^2/2000\) para duas estratégias de crescimento

Experimento

Quantas cópias custam n inserções num vetor que dobra e num que cresce 1000 posições por vez?

Dobrar custa \(\approx 2n\) cópias: \textbf{amortizado \(O(1)\)} por inserção. Somar uma constante custa \(\approx n^2/2000\): quadrático, por maior que seja a constante. Por isso std::vector, ArrayList e as listas do Python crescem por um fator (1,5 ou 2), nunca por uma quantidade fixa.

Conexões

  • Linguagens: A lista do Python cresce cerca de 12%, o vector do C++ (GCC) dobra e o ArrayList do Java cresce 50%: fatores diferentes, todos amortizados \(O(1)\).
  • Bancos de dados: Tabelas hash de bancos e caches (Redis) dobram de tamanho e reinserem tudo, às vezes aos poucos (rehash incremental) para não travar o serviço.
  • Redes: O TCP aumenta a janela de envio por fator e a reduz pela metade em congestionamento: crescimento multiplicativo, como o vetor dinâmico.
  • Sistemas embarcados: Em tempo real, um pico de \(\Theta(n)\) numa cópia pode estourar um prazo: lá se prefere capacidade fixa ou crescimento sem cópia (listas de blocos).
14 Listas simplesmente ligadas Cormen, seção 10.2

Objetivos

  • Representar uma sequência com nós e ponteiros
  • Inserir no início e no fim, buscar e remover
  • Inverter uma lista sem memória extra
  • Medir a vantagem da lista na inserção e o custo do cache no percurso

Experimento

Quanto custa inserir no início de um vetor? E percorrer uma lista ligada cujos nós estão espalhados pela memória?

Inserir no início confirma a teoria: \(n(n-1)/2\) movimentos no vetor, nenhum na lista. No percurso, os dois são \(\Theta(n)\), mas a lista espalhada fica até 200 vezes mais lenta que o vetor quando sai do cache. O modelo RAM não vê a hierarquia de memória; a medição vê.

Conexões

  • Sistemas operacionais: Filas de processos, listas de arquivos abertos e de páginas livres são listas ligadas dentro do kernel; a inserção \(O(1)\) é essencial.
  • Alocação de memória: malloc encadeia os blocos livres numa lista; programas em C que vazam memória costumam “perder a cabeça de uma lista.
  • Arquitetura de computadores: O experimento mostra a hierarquia de memória: L1 (\(\approx\)1 ns), L2, L3 e RAM (\(\approx\)100 ns). Estruturas cache-friendly vencem na prática.
  • Linguagens funcionais: Em Lisp, Haskell e Erlang a lista ligada é a estrutura básica: inserir na cabeça cria uma lista nova compartilhando o resto, sem copiar nada.
15 Listas duplamente ligadas e listas circulares Cormen, seção 10.2

Objetivos

  • Implementar a lista duplamente ligada e seus casos especiais sem sentinela
  • Remover um nó dado em \(O(1)\)
  • Fechar a lista num círculo e percorrê-la sem fim
  • Resolver o problema de Josefo e comparar lista circular com vetor

Experimento

Quantos passos a lista circular e quantos deslocamentos o vetor gastam para resolver o problema de Josefo?

A lista circular resolve Josefo em exatamente \((n-1)(k-1)\) passos; o vetor, em \(\approx 0{,}3\,n^2\) deslocamentos: 4.800 vezes mais trabalho para \(n = 32.000\). Quando a operação dominante é remover no meio, a lista ligada vence; quando é acessar por posição, o vetor vence.

Conexões

  • Sistemas operacionais: O kernel Linux usa listas duplas circulares (list_head) em toda parte; o escalonador round robin percorre uma lista circular de processos.
  • Navegadores: O histórico com voltar e avançar e as abas em ordem são listas duplas: sair de uma aba no meio é remover um nó em \(O(1)\).
  • Caches LRU: Um cache LRU combina uma tabela hash com uma lista dupla: o item usado vai para a frente, o mais antigo sai do fim, tudo em \(O(1)\).
  • Jogos: Turnos de jogadores, animações em laço e trilhas de áudio em repetição são percorridos como listas circulares.
16 Sentinelas e listas sem ponteiros Cormen, seções 10.2 e 10.3

Objetivos

  • Usar uma sentinela para eliminar os casos especiais das listas
  • Implementar a lista circular duplamente ligada com sentinela do livro
  • Representar listas com vetores e gerenciar posições livres
  • Medir quantos testes a sentinela economiza numa busca

Experimento

Quantos testes uma busca sem sucesso faz com e sem sentinela? E quanto tempo?

A sentinela corta os testes exatamente pela metade (\(n + 1\) contra \(2n + 1\)) e economiza cerca de 10% do tempo. Mais importante: elimina os casos especiais de lista vazia, cabeça e cauda, que são a fonte da maioria dos erros com listas. Na representação em vetores, a lista livre faz o papel do malloc: alocar e liberar em \(O(1)\).

Conexões

  • Kernel Linux: Toda lista do kernel (list_head) é circular, dupla e com sentinela: a “cabeça é um nó sem dados, exatamente \(L.\mathit{nil}\).
  • Alocadores de memória: malloc mantém listas livres de blocos por tamanho; alocadores de pool (jogos, servidores) usam a pilha livre da Figura 10.7.
  • Sistemas de arquivos: O FAT do MS-DOS e de pendrives é uma lista em vetor: cada posição diz o próximo bloco do arquivo; os blocos livres formam outra lista.
  • Bancos de dados: Páginas livres de um arquivo de banco são encadeadas numa lista livre guardada no próprio arquivo, com índices no lugar de ponteiros.
17 Filas de prioridade Cormen, seção 6.5

Objetivos

  • Definir as operações de uma fila de prioridade
  • Implementar inserção, extração do máximo e aumento de chave num heap
  • Comparar heap, vetor ordenado e vetor desordenado como filas de prioridade
  • Medir \(\Theta(n\lg n)\) contra \(\Theta(n^2)\) em \(n\) inserções e \(n\) extrações

Experimento

Para n inserções seguidas de n extrações, quantas comparações fazem heap, vetor ordenado e vetor desordenado?

O vetor desordenado faz exatamente \(n(n-1)/2\) comparações e o ordenado, \(n^2/4\); o heap fica em \(\approx 1{,}8\,n\lg n\). Para \(n = 16.000\), o heap é 150 vezes mais barato que o vetor ordenado. A estrutura certa muda a ordem de crescimento.

Conexões

  • Sistemas operacionais: Escalonadores escolhem o próximo processo pela prioridade; temporizadores do kernel disparam o evento mais próximo: Extract-Min num heap.
  • Redes de computadores: Roteadores com QoS servem primeiro os pacotes de voz e vídeo; o roteamento OSPF roda Dijkstra com fila de prioridade em cada roteador.
  • Simulação de eventos: Simuladores (filas de banco, tráfego, epidemias) processam eventos em ordem de tempo: o próximo evento sai de uma fila de mínimo.
  • Compressão de dados: O código de Huffman (aula 34) junta repetidamente os dois símbolos menos frequentes, retirados de uma fila de prioridade.
18 Projeto 2: simulador de escalonamento de processos Projeto

Apresentação dos projetos do Bloco 2. Cada equipe constrói um simulador de escalonamento de CPU com as estruturas do bloco e compara políticas com experimentos.

Entregas

  • Simulador em C: processos chegam com tempo de execução e prioridade; fila circular (FIFO e round robin), lista duplamente ligada com sentinela (processos bloqueados), pilha (chamadas de cada processo) e fila de prioridade em heap (menor trabalho primeiro)
  • Três políticas: FIFO, menor trabalho primeiro (heap) e round robin (fila circular)
  • Experimento: tempo médio de espera e vazão para 1.000 a 1.000.000 de processos, com o custo das operações confrontado com a teoria
  • Relatório: qual política vence em cada carga e quanto custa cada estrutura (gráficos com curvas teóricas)
  • Apresentação de 10 minutos com demonstração ao vivo

Bloco 3 Tabelas hash e árvores

Tabelas hash (encadeamento, funções hash, endereçamento aberto), árvores binárias de busca, AVL, rubro-negras e árvores B. Termina com o Projeto 3.

19 Tabelas hash com encadeamento Cormen, seções 11.1 e 11.2

Objetivos

  • Explicar endereçamento direto e por que ele não escala
  • Resolver colisões por encadeamento
  • Analisar a busca em função do fator de carga \(\alpha = n/m\)
  • Medir \(\alpha\) e \(1 + \alpha/2\) elementos examinados por busca

Experimento

Quantos elementos uma busca examina, com e sem sucesso, em função do fator de carga α?

Os teoremas 11.1 e 11.2 se confirmam com desvio menor que 1%: \(\alpha\) elementos sem sucesso, \(1 + \alpha/2\) com sucesso. O custo depende de \(\alpha\), \textbf{não de \(n\)}: com \(\alpha\) fixo, uma tabela com um bilhão de chaves busca tão rápido quanto uma com mil.

Conexões

  • Bancos de dados: Índices hash respondem WHERE id = ? em \(O(1)\); o hash join monta uma tabela hash com a menor relação e varre a maior.
  • Compiladores e linguagens: A tabela de símbolos guarda identificadores numa tabela hash; dicionários do Python, objetos do JavaScript e HashMap do Java também.
  • Cache distribuído: Memcached e Redis distribuem chaves entre servidores com hash; o hashing consistente minimiza as chaves movidas quando um servidor entra ou sai.
  • Redes: Switches mantêm a tabela de endereços MAC em hash; roteadores e firewalls classificam fluxos (5-tupla) por hash para decidir em nanossegundos.
20 Funções hash e hashing universal Cormen, seção 11.3

Objetivos

  • Construir funções hash pelos métodos da divisão e da multiplicação
  • Reconhecer escolhas ruins de \(m\) (potências de 2 com chaves estruturadas)
  • Definir hashing universal e o que ele garante
  • Medir o efeito da função hash em chaves com padrão

Experimento

Com chaves múltiplas de 64 e fator de carga 1, quanto custa uma busca com cada função hash?

A escolha da função muda o custo em mais de 30 vezes com as mesmas chaves: dividir por potência de 2 dá exatamente 32,5; dividir por um primo dá 1. O hashing universal fica em 1,5 em média, como garante o teorema, sem depender das chaves. Uma função isolada sorteada, porém, pode ter azar: a garantia é sobre a média da família.

Conexões

  • Segurança: Em 2011, ataques de hash flooding derrubaram servidores web enviando parâmetros que colidiam; Python, Ruby e Java passaram a sortear a função (SipHash).
  • Sistemas distribuídos: Hashing consistente (Dynamo, Cassandra) e rendezvous hashing decidem qual servidor guarda cada chave; uma função ruim desequilibra o cluster.
  • Big data: Contagem aproximada (HyperLogLog), filtros de Bloom e count-min sketch dependem de funções hash independentes e bem espalhadas.
  • Criptografia: Hashes criptográficos (SHA-256) vão além: resistência a colisões e a pré-imagem. Senhas, assinaturas e blockchain dependem disso.
21 Endereçamento aberto Cormen, seção 11.4

Objetivos

  • Guardar todas as chaves no próprio vetor, sem listas
  • Comparar sondagem linear, quadrática e hash duplo
  • Explicar por que a remoção exige marcas
  • Medir as sondagens e confrontar com \(1/(1-\alpha)\) e com a fórmula de Knuth

Experimento

Quantas sondagens fazem a sondagem linear e o hash duplo, com e sem sucesso, para α de 0,05 a 0,9?

As quatro fórmulas se confirmam com desvios de poucos por cento. Até \(\alpha \approx 0{,}5\), linear e duplo empatam; acima disso, os aglomerados da linear explodem o custo. Regra prática: endereçamento aberto com \(\alpha \le 0{,}7\); acima disso, redimensione.

Conexões

  • Hardware: Caches de CPU são tabelas hash em hardware: o endereço escolhe o conjunto, e cada conjunto tem poucas “posições (associatividade).
  • Linguagens: O HashMap do Rust (SwissTable), o dict do Python e o absl::flat_hash_map do C++ usam endereçamento aberto pela localidade de memória.
  • Sistemas embarcados: Sem malloc nem listas: uma tabela de tamanho fixo com endereçamento aberto cabe em memória estática, ideal para microcontroladores.
  • Bancos de dados: Agregações (GROUP BY) e joins em memória usam tabelas com sondagem linear vetorizada (SIMD) em sistemas analíticos.
22 Árvores binárias de busca: percursos e consultas Cormen, seções 12.1 e 12.2

Objetivos

  • Enunciar a propriedade de árvore binária de busca
  • Percorrer em ordem, pré-ordem e pós-ordem
  • Buscar, achar mínimo, máximo, sucessor e predecessor em \(O(h)\)
  • Medir o custo médio de busca em árvores aleatórias: \(\approx 1{,}39\lg n\)

Experimento

Numa ABB construída com chaves em ordem aleatória, quantos nós uma busca com sucesso visita em média?

O custo médio segue \(2(1 + 1/n)H_n - 3\) com desvio abaixo de 1%: para dois milhões de chaves, 27 nós por busca. Uma árvore aleatória é só 39% mais funda (em média) que uma perfeitamente balanceada. O perigo está nas entradas não aleatórias (próxima aula).

Conexões

  • Sistemas de arquivos: Diretórios são árvores; ext4 e Btrfs indexam nomes em árvores de busca para achar um arquivo entre milhões sem varrer todos.
  • Compiladores: Árvores sintáticas são percorridas em pós-ordem para gerar código e avaliar expressões; o percurso em ordem imprime a expressão infixa.
  • Bancos de dados: Consultas por intervalo (BETWEEN) usam o sucessor: acha-se o início e anda-se em ordem. Hash não faz isso; árvores fazem.
  • Geometria computacional: Árvores k-d e de intervalos generalizam a ABB para pontos no plano: “quais restaurantes estão neste retângulo do mapa?.
23 Inserção, remoção e altura das árvores de busca Cormen, seções 12.3 e 12.4

Objetivos

  • Inserir mantendo a propriedade de ABB
  • Remover nos três casos com Transplant
  • Entender por que a altura esperada de uma ABB aleatória é \(O(\lg n)\)
  • Medir a altura de árvores aleatórias e o desastre da entrada ordenada

Experimento

Qual a altura de uma ABB com chaves aleatórias? E com chaves em ordem crescente?

Com chaves aleatórias, a altura fica sempre entre \(\lg n\) e \(3\lg n\): para dois milhões de chaves, cerca de 54 níveis. Com chaves em ordem, exatamente \(n - 1\): dois milhões de níveis. A mesma estrutura, com a mesma quantidade de dados, fica 40.000 vezes mais funda só pela ordem de chegada.

Conexões

  • Bancos de dados: Chaves autoincrementadas chegam em ordem: é exatamente o pior caso de uma ABB comum. Por isso bancos usam árvores balanceadas (B-trees).
  • Editores e cordas: Editores de texto grandes guardam o documento numa árvore de trechos (rope); inserir e remover no meio custa \(O(h)\).
  • Jogos: Placares (leaderboards) mantêm jogadores ordenados por pontuação com inserção e remoção constantes; a altura define a latência.
  • Redes: Tabelas de roteamento e de temporizadores de conexões TCP mudam o tempo todo: remoções e inserções eficientes são essenciais.
24 Árvores AVL Cormen, problema 13-3 e seção 13.2 (rotações)

Objetivos

  • Definir o fator de balanceamento AVL
  • Aplicar rotações simples e duplas
  • Provar que a altura de uma AVL é \(O(\lg n)\) (árvores de Fibonacci)
  • Medir a altura com chaves em ordem e o número de rotações por inserção

Experimento

Qual a altura de uma AVL com chaves em ordem e com chaves aleatórias? Quantas rotações custa cada inserção?

Com chaves em ordem, a AVL tem altura exatamente \(\lg n\), contra \(n - 1\) da ABB comum (21 níveis contra dois milhões). Com chaves aleatórias, fica abaixo de \(1{,}44\lg n\), e o reequilíbrio custa só \(\approx 0{,}7\) rotação por inserção.

Conexões

  • Bancos em memória: Bancos e caches em memória que priorizam leituras usam árvores AVL ou parecidas: a altura menor que a da rubro-negra acelera buscas.
  • Tempo real: Garantia de \(O(\lg n)\) no pior caso, sem amortização: adequada a sistemas que não podem ter picos de latência.
  • Linguagens funcionais: Conjuntos e mapas imutáveis (Data.Map em Haskell, Set em OCaml) usam árvores balanceadas por tamanho ou altura, parentes da AVL.
  • Roteamento: Tabelas de prefixos e estruturas de intervalos em roteadores usam árvores balanceadas para manter a busca curta mesmo sob atualizações constantes.
25 Árvores rubro-negras Cormen, seções 13.1 a 13.3

Objetivos

  • Enunciar as cinco propriedades rubro-negras
  • Provar que a altura é no máximo \(2\lg(n+1)\)
  • Executar RB-Insert-Fixup nos três casos
  • Medir altura e rotações e conferir as propriedades a cada experimento

Experimento

A altura de uma rubro-negra respeita 2 lg(n+1)? Quantas rotações custa uma inserção?

As propriedades valeram em todas as árvores, e a altura ficou sempre abaixo de \(2\lg(n + 1)\), mesmo com chaves em ordem. O conserto custa \(\approx 0{,}58\) rotação por inserção: menos que a AVL, ao preço de árvores um pouco mais altas. É o equilíbrio que levou as bibliotecas padrão a escolher a rubro-negra.

Conexões

  • Kernel Linux: O escalonador CFS ordenava processos numa rubro-negra pelo tempo de CPU usado; a memória virtual guarda as áreas mapeadas de cada processo numa delas.
  • Bibliotecas padrão: std::map e std::set do C++ e TreeMap do Java são rubro-negras; o HashMap do Java vira rubro-negra quando uma lista fica longa.
  • Redes: Temporizadores de alta resolução e tabelas de conexões usam rubro-negras para inserções e remoções frequentes com pior caso garantido.
  • Estruturas persistentes: Rubro-negras funcionais (Okasaki) são a base de mapas imutáveis em Scala e Clojure: versões antigas continuam válidas a custo \(O(\lg n)\).
26 Árvores B e memória secundária Cormen, capítulo 18 (seções 18.1 e 18.2)

Objetivos

  • Explicar por que o acesso ao disco muda o modelo de custo
  • Definir árvore B de grau mínimo \(t\) e limitar sua altura
  • Inserir com divisão preventiva de nós cheios
  • Medir a altura para \(t = 2\), 16 e 128 e conferir \(\log_t\frac{n+1}2\)

Experimento

Qual a altura de uma árvore B com t = 2, 16 e 128 para até 4 milhões de chaves?

A altura respeitou o teorema 18.1 em todos os casos. Com \(t = 128\), quatro milhões de chaves ficam em 3 níveis: uma busca lê 3 páginas do disco, contra 22 de uma árvore binária balanceada. É por isso que todo banco de dados relacional indexa com árvores B (na verdade, B+).

Conexões

  • Bancos de dados: PostgreSQL, MySQL (InnoDB), SQLite e Oracle indexam tabelas com árvores B+: as chaves nas folhas, ligadas em lista para consultas por intervalo.
  • Sistemas de arquivos: NTFS, HFS+, APFS, Btrfs e XFS guardam diretórios e metadados em árvores B: abrir um arquivo entre milhões custa poucas leituras.
  • Armazenamento chave-valor: LMDB e BoltDB são árvores B mapeadas em memória; MongoDB (WiredTiger) e CouchDB também usam árvores B.
  • SSDs e cache: Em SSDs, escrever custa mais que ler: variantes como B\(^\varepsilon\)-trees e LSM-trees (aula 4) trocam leituras por menos escritas.
27 Projeto 3: mecanismo de busca em miniatura Projeto

Apresentação dos projetos do Bloco 3. Cada equipe indexa um conjunto de textos em português com as estruturas do bloco e compara as estruturas experimentalmente.

Entregas

  • Índice invertido em C: palavra → lista de documentos e posições, sobre livros de domínio público (Machado de Assis, por exemplo)
  • O mesmo índice com quatro dicionários: hash com encadeamento, hash com endereçamento aberto, ABB e uma árvore balanceada (AVL ou rubro-negra)
  • Experimento: tempo e número de comparações por busca, altura das árvores e tamanho das listas, para 10 mil a 10 milhões de palavras; confronto com a teoria
  • Consultas por prefixo e por intervalo alfabético: quais estruturas permitem e a que custo
  • Apresentação de 10 minutos com demonstração de buscas ao vivo

Bloco 4 Grafos

Buscas em largura e em profundidade, ordenação topológica, árvores geradoras mínimas e caminhos mínimos; programação dinâmica, algoritmos gulosos e casamento de cadeias. Termina com o Projeto 4.

28 Grafos e busca em largura Cormen, seções 22.1 e 22.2

Objetivos

  • Representar grafos por listas de adjacência e por matriz de adjacência
  • Executar a busca em largura e obter distâncias e a árvore de caminhos mínimos
  • Provar que a BFS custa \(O(V + E)\)
  • Medir o custo linear e o “mundo pequeno de grafos aleatórios

Experimento

A BFS faz exatamente V + 2E operações? Qual a distância média num grafo aleatório com grau médio 10?

A BFS faz exatamente \(V + 2E\) operações: linear. E grafos aleatórios são “pequenos”: com dois milhões de vértices e grau médio 10, a distância média é 6,4, crescendo como \(\ln V\). É o fenômeno dos seis graus de separação, medido por BFS em redes sociais reais.

Conexões

  • Redes sociais: O Facebook mediu, com BFS em 721 milhões de usuários, uma distância média de 4,74; “pessoas que você talvez conheça são vértices à distância 2.
  • Web e buscadores: Rastreadores (crawlers) percorrem a web em largura a partir de páginas-semente: a BFS encontra primeiro as páginas mais próximas e relevantes.
  • Redes de computadores: Broadcast e descoberta de vizinhos inundam a rede camada por camada; o traceroute revela as camadas da BFS da internet.
  • Jogos e robótica: Em mapas em grade sem pesos, a BFS dá o menor caminho de um personagem ou robô; o cubo de Rubik foi resolvido (“número de Deus = 20) com buscas em largura.
29 Busca em profundidade e ordenação topológica Cormen, seções 22.3 e 22.4

Objetivos

  • Executar a DFS e registrar tempos de descoberta e de término
  • Classificar arestas (árvore, retorno, avanço, cruzamento)
  • Ordenar topologicamente um grafo acíclico pela ordem de término
  • Medir \(V + E\) operações e o efeito do cache no tempo

Experimento

A DFS faz exatamente V + E operações num DAG? O tempo também cresce linearmente?

A contagem é exatamente \(V + E\) e a ordem topológica é sempre válida. O tempo, porém, só é linear enquanto o grafo cabe no cache: depois, cada acesso aleatório custa uma ida à RAM. Bibliotecas de grafos de alto desempenho renumeram os vértices para que vizinhos fiquem próximos na memória.

Conexões

  • Compiladores e builds: make, Maven, Bazel e gerenciadores de pacotes (npm, apt) ordenam topologicamente as dependências; um ciclo é um erro de configuração.
  • Planilhas: Ao mudar uma célula, a planilha recalcula as dependentes em ordem topológica e detecta referências circulares com a DFS.
  • Sistemas operacionais: Detectar deadlock é achar um ciclo no grafo de espera entre processos e recursos: uma aresta de retorno na DFS.
  • Gestão de projetos e dados: Diagramas PERT/CPM, pipelines de dados (Airflow) e redes neurais (o grafo de computação do PyTorch) são DAGs executados em ordem topológica.
30 Conjuntos disjuntos e árvores geradoras mínimas Cormen, capítulo 21 e capítulo 23

Objetivos

  • Implementar conjuntos disjuntos com união por posto e compressão de caminho
  • Enunciar a propriedade de corte que justifica os algoritmos gulosos de AGM
  • Executar Kruskal e Prim
  • Medir o custo quase constante das operações de conjuntos disjuntos

Experimento

Quantos ponteiros uma operação de conjuntos disjuntos segue, sem e com as heurísticas?

Sem heurísticas, cada operação custa \(\approx 2n/15\): com 16 mil elementos, mais de 2 mil passos. Com união por posto e compressão de caminho, o custo cai para 0,6 e não muda até dois milhões de elementos. É o que torna o Kruskal \(O(E\lg V)\): o gargalo passa a ser só a ordenação das arestas.

Conexões

  • Redes de infraestrutura: Projetar redes elétricas, de fibra óptica ou de saneamento ligando todos os pontos com o menor custo de cabo é uma AGM (Borůvka, 1926, eletrificação da Morávia).
  • Agrupamento de dados: Remover as \(k - 1\) arestas mais pesadas da AGM separa os dados em \(k\) grupos: o agrupamento por ligação simples (single linkage).
  • Visão computacional: A segmentação de imagens de Felzenszwalb e Huttenlocher une pixels parecidos com conjuntos disjuntos, no estilo do Kruskal.
  • Compiladores e redes: Conjuntos disjuntos resolvem unificação de tipos (inferência de tipos) e acompanham componentes conexas de redes que mudam em tempo real.
31 Caminhos mínimos: Bellman-Ford e Dijkstra Cormen, seções 24.1 e 24.3

Objetivos

  • Definir caminhos mínimos com pesos e o papel dos ciclos negativos
  • Usar o relaxamento como operação básica
  • Executar Bellman-Ford (\(O(VE)\)) e Dijkstra (\(O((V + E)\lg V)\) com heap)
  • Medir vetor contra heap no Dijkstra e o custo do Bellman-Ford

Experimento

Em grafos esparsos, quantas operações fazem Dijkstra com vetor, Dijkstra com heap e Bellman-Ford?

As contagens batem: \(V^2 + E\) e \((V-1)E\) exatos, e \(\Theta(V\lg V)\) com heap. Em grafos esparsos, o heap é 130 vezes mais barato que o vetor para \(V = 4.000\). Em grafos densos (\(E \approx V^2\)), o vetor volta a ser competitivo: a melhor estrutura depende da densidade do grafo.

Conexões

  • Navegação (GPS): Google Maps e Waze usam Dijkstra com acelerações (A*, hierarquias de contração) em grafos de bilhões de arestas, com pesos que mudam com o trânsito.
  • Roteamento na internet: OSPF e IS-IS rodam Dijkstra em cada roteador; RIP e BGP são variações distribuídas do Bellman-Ford (vetor de distâncias).
  • Finanças: Com pesos \(-\log(\text{câmbio})\), um ciclo negativo é uma oportunidade de arbitragem entre moedas: Bellman-Ford a detecta.
  • Logística: Roteirização de entregas e planejamento de rotas de transporte público começam com caminhos mínimos entre todos os pontos de interesse.
32 Caminhos mínimos entre todos os pares: Floyd-Warshall Cormen, seção 25.2

Objetivos

  • Formular caminhos mínimos entre todos os pares por programação dinâmica
  • Executar Floyd-Warshall e reconstruir caminhos
  • Calcular o fecho transitivo
  • Medir \(V^3\) contra \(V\) execuções de Dijkstra em grafos esparsos

Experimento

Em grafos esparsos, Floyd-Warshall (V³) ou V execuções de Dijkstra (V²lg V)?

As duas formas de crescimento se confirmam: \(V^3\) para Floyd-Warshall e \(V^2\lg V\) para \(V\) execuções de Dijkstra, com as mesmas respostas. Em grafos esparsos, Dijkstra repetido vence; em grafos densos ou pequenos, a simplicidade do Floyd-Warshall compensa, e ele aceita pesos negativos.

Conexões

  • Redes: Matrizes de latência entre todos os servidores de um data center ou de uma CDN orientam onde colocar réplicas e como balancear carga.
  • Bioinformática: Distâncias entre todas as espécies (ou genes) alimentam a construção de árvores filogenéticas por métodos de distância.
  • Mapas e logística: Tabelas de distância entre todas as cidades (o “mapa rodoviário) são pré-calculadas para planejar rotas e entregas.
  • Análise de dependências: O fecho transitivo responde “A depende de B, direta ou indiretamente? em compiladores, bancos de dados (consultas recursivas) e controle de acesso.
33 Programação dinâmica Cormen, seções 15.1, 15.3 e 15.4

Objetivos

  • Reconhecer subestrutura ótima e subproblemas sobrepostos
  • Resolver o corte de hastes de cima para baixo (memoização) e de baixo para cima
  • Calcular a maior subsequência comum (LCS) e reconstruí-la
  • Medir \(2^n\) chamadas contra \(n(n+1)/2\)

Experimento

Quantas chamadas faz o corte de hastes recursivo ingênuo e quantas faz a versão memoizada?

As contagens são exatamente \(2^n\) e \(n(n+1)/2 + 1\). Guardar as respostas dos subproblemas transforma um algoritmo exponencial em quadrático, com o mesmo resultado. A programação dinâmica troca memória por tempo: uma tabela de \(n + 1\) posições economiza bilhões de chamadas.

Conexões

  • Bioinformática: Alinhar sequências de DNA e proteínas (Needleman–Wunsch, Smith–Waterman) é uma LCS com pesos: a base do BLAST e da comparação de genomas.
  • Controle de versão: diff e git diff calculam a menor sequência de edições entre dois arquivos, uma variação da LCS.
  • Linguagem natural: Corretores ortográficos (distância de edição), reconhecimento de voz (Viterbi) e análise sintática (CYK) são programação dinâmica.
  • Finanças e IA: Precificação de opções em árvores binomiais e aprendizado por reforço (equação de Bellman) resolvem subproblemas de baixo para cima.
34 Algoritmos gulosos: seleção de atividades e Huffman Cormen, seções 16.1 a 16.3

Objetivos

  • Reconhecer a propriedade da escolha gulosa e a subestrutura ótima
  • Resolver a seleção de atividades escolhendo sempre a que termina primeiro
  • Construir códigos de Huffman com fila de prioridade
  • Medir o comprimento médio de Huffman contra a entropia de Shannon

Experimento

O comprimento médio do código de Huffman fica entre a entropia H e H + 1? Quanto ele ganha do código fixo?

Em todos os alfabetos, \(H \le L < H + 1\), e na prática \(L - H < 0{,}1\) bit: Huffman fica praticamente no limite teórico da compressão sem perdas símbolo a símbolo. Um algoritmo guloso de cinco linhas atinge o ótimo, e a teoria da informação diz que ninguém pode fazer melhor.

Conexões

  • Compressão: DEFLATE (ZIP, gzip, PNG), JPEG e MP3 usam códigos de Huffman na etapa final; formatos modernos usam codificação aritmética, que chega ainda mais perto de \(H\).
  • Redes: O HTTP/2 comprime cabeçalhos com HPACK, que usa uma tabela de Huffman fixa: cada requisição web economiza bytes.
  • Agendamento: Escalonar tarefas por prazo (earliest deadline first) e alocar salas, frequências de rádio ou pistas de pouso são problemas gulosos parentes da seleção de atividades.
  • Aprendizado de máquina: Árvores de decisão escolhem gulosamente o atributo de maior ganho de informação (entropia!); o hierarchical softmax usa árvores de Huffman.
35 Casamento de cadeias: o algoritmo de Knuth-Morris-Pratt Cormen, seções 32.1 e 32.4

Objetivos

  • Definir o problema de casamento de cadeias e o algoritmo ingênuo
  • Calcular a função de prefixo \(\pi\) de um padrão
  • Executar o KMP e mostrar que ele é \(\Theta(n + m)\)
  • Medir o pior caso do ingênuo contra o KMP

Experimento

Com texto aⁿ e padrão a⁹⁹b, quantas comparações fazem o algoritmo ingênuo e o KMP?

O ingênuo faz exatamente \((n - m + 1)m\) comparações no pior caso; o KMP fica abaixo de \(3n\), \textbf{independente de \(m\)}. A função de prefixo é uma pequena tabela que evita reler o texto: mais uma vez, pré-processar troca memória por tempo.

Conexões

  • Busca em texto: grep, o “localizar dos editores e navegadores e as bibliotecas de strings usam KMP, Boyer-Moore ou variações para buscar em gigabytes.
  • Segurança: Antivírus e sistemas de detecção de intrusão (Snort, Suricata) procuram milhares de assinaturas ao mesmo tempo com Aho-Corasick, a generalização do KMP.
  • Bioinformática: Localizar motivos em DNA, sítios de restrição e sequências repetidas: casamento de cadeias sobre o alfabeto \(\{A, C, G, T\}\).
  • Compiladores: Analisadores léxicos reconhecem palavras-chave e tokens com autômatos finitos, o mesmo modelo da seção 32.3.
36 Projeto 4: rotas e redes numa cidade real Projeto

Apresentação dos projetos finais. Cada equipe monta o grafo de ruas de um bairro de Salvador (OpenStreetMap) e resolve problemas reais com os algoritmos do bloco, comparando teoria e medições.

Entregas

  • Grafo de ruas extraído do OpenStreetMap (vértices = cruzamentos, arestas = trechos com comprimento), com pelo menos 10 mil vértices
  • Menor caminho entre dois endereços com BFS (número de quarteirões), Dijkstra com vetor e com heap, e Bellman-Ford; tempos medidos e confrontados com a teoria
  • Rede de menor custo ligando pontos de interesse (escolas, postos de saúde) com Kruskal ou Prim
  • Uma técnica de projeto aplicada: programação dinâmica (por exemplo, rota que visita pontos em ordem) ou gulosa (por exemplo, cobertura de pontos)
  • Apresentação de 10 minutos com mapa e demonstração ao vivo

Bloco complementar Ordenação avançada (opcional)

Cinco módulos opcionais, fora das 36 aulas, para quem quer ir além do bloco 1: quicksort, quicksort aleatorizado, limite inferior, ordenação em tempo linear e seleção.

C1 Quicksort OpcionalCormen, seções 7.1 e 7.2

Objetivos

  • Executar Partition e provar sua corretude pelo invariante das quatro regiões
  • Analisar o pior caso (\(\Theta(n^2)\)) e o melhor caso (\(\Theta(n\lg n)\))
  • Entender por que partições desbalanceadas em proporção constante ainda dão \(n\lg n\)
  • Medir \(\approx 2n\ln n\) comparações em entradas aleatórias e \(n^2/2\) em entradas ordenadas

Experimento

Quantas comparações o quicksort faz em vetores aleatórios e em vetores já ordenados?

A previsão de caso médio, \(2(n+1)H_n - 4n \approx 1{,}39\,n\lg n\), se confirma com desvio de cerca de 1%. E o pior caso é exatamente \(n(n-1)/2\), justamente na entrada mais “fácil”, o vetor ordenado. A próxima aula elimina esse risco com aleatoriedade.

Conexões

  • Bibliotecas padrão: qsort (C), std::sort (introsort em C++), Arrays.sort para primitivos (quicksort de dois pivôs no Java) e sort do Go (pdqsort).
  • Bancos de dados: Ordenar resultados em memória (ORDER BY) e preparar joins usa quicksort ou variações; a partição também aparece na seleção do \(k\)-ésimo (LIMIT).
  • Segurança: Um adversário que conhece o pivô pode montar entradas \(\Theta(n^2)\) (“McIlroy killer): por isso as bibliotecas aleatorizam ou usam introsort.
  • Computação paralela: Depois de particionar, as duas partes são independentes; GPUs ordenam com variações paralelas da partição.
C2 Quicksort aleatorizado e a análise do caso médio OpcionalCormen, seções 7.3 e 7.4

Objetivos

  • Aleatorizar a escolha do pivô e entender o que isso garante
  • Calcular o número esperado de comparações com variáveis indicadoras
  • Distinguir caso médio (sobre entradas) de tempo esperado (sobre sorteios)
  • Medir a concentração das comparações em torno da esperança

Experimento

Com entrada já ordenada, o pivô sorteado faz o quicksort voltar a 2(n+1)H_n − 4n comparações?

Na entrada que destrói o pivô fixo (\(n(n-1)/2\) comparações), o pivô sorteado volta à esperança \(2(n+1)H_n - 4n\). O histograma mostra execuções concentradas: a probabilidade de algo perto do pior caso é desprezível. Aleatoriedade troca uma entrada ruim por uma sorte ruim, e sorte ruim é rara.

Conexões

  • Segurança: Tabelas hash de linguagens (Python, Rust) sorteiam a semente da função hash a cada execução pelo mesmo motivo: impedir que um atacante escolha a entrada do pior caso.
  • Redes: O Ethernet usa recuo exponencial aleatório após colisões; balanceadores de carga usam “a melhor de duas escolhas aleatórias para distribuir requisições.
  • Aprendizado de máquina: Gradiente estocástico, random forests e dropout: a aleatoriedade torna algoritmos mais rápidos e mais robustos, com garantias em esperança.
  • Sistemas distribuídos: Protocolos de fofoca (gossip) e eleição de líder no Raft usam tempos aleatórios para evitar empates e espalhar informação em \(O(\lg n)\) rodadas.
C3 O limite inferior da ordenação por comparação OpcionalCormen, seção 8.1

Objetivos

  • Modelar algoritmos de ordenação por comparação como árvores de decisão
  • Provar que todo algoritmo desse tipo faz \(\Omega(n\lg n)\) comparações no pior caso
  • Usar a aproximação de Stirling para \(\lg(n!)\)
  • Medir quão perto do limite o merge sort chega

Experimento

O pior caso do merge sort fica acima de ⌈lg n!⌉? Quão perto do limite ele chega em média?

Em todos os \(n\) testados, o pior caso do merge sort ficou acima de \(\lceil\lg n!\rceil\), como o teorema exige, e para \(n \le 4\) ele atinge o limite. Para \(n = 2^{20}\), o merge sort faz só 1% mais comparações que \(\lg n!\). Não há como fazer muito melhor comparando; para ir além, é preciso usar a estrutura das chaves.

Conexões

  • Teoria da informação: O argumento é de Shannon: uma comparação gera no máximo 1 bit. O mesmo raciocínio dá limites para busca (\(\lg n\)) e para jogos como “vinte perguntas.
  • Compressão: Nenhum compressor sem perdas reduz todos os arquivos: há \(2^n\) arquivos de \(n\) bits e menos códigos menores. É o argumento de contagem das folhas.
  • Criptografia: Uma chave de 128 bits tem \(2^{128}\) possibilidades: um ataque de força bruta precisa de \(\Omega(2^{127})\) tentativas em média. Limites inferiores são a base da segurança.
  • Computação quântica: O algoritmo de Grover busca em \(O(\sqrt n)\), e há prova de que nenhum algoritmo quântico faz melhor: limites inferiores também valem no modelo quântico.
C4 Ordenação em tempo linear: contagem e radix OpcionalCormen, seções 8.2 e 8.3

Objetivos

  • Ordenar inteiros em \(0..k\) por contagem em \(\Theta(n + k)\)
  • Explicar por que a estabilidade é essencial para o radix sort
  • Ordenar \(d\) dígitos em \(\Theta(d(n + k))\) e escolher a base
  • Medir tempo por elemento: constante no radix, \(\lg n\) no qsort

Experimento

O tempo por elemento do radix sort fica constante enquanto o do qsort cresce como lg n?

O tempo por elemento do radix sort é praticamente constante (linear no total) e mais de 10 vezes menor que o do qsort, cujo custo por elemento cresce como \(\lg n\). O limite \(\Omega(n\lg n)\) não se aplica porque o radix sort não compara chaves: usa os bits delas.

Conexões

  • GPUs: Radix sort é o padrão para ordenar em GPU (CUB, Thrust): as contagens e somas de prefixo paralelizam muito bem.
  • Bancos de dados: Ordenar por chaves inteiras (datas, códigos) com radix e agrupar por contagem (GROUP BY) aparecem em bancos colunares como DuckDB.
  • Bioinformática: Arranjos de sufixos de genomas são construídos com radix sort sobre o alfabeto \(\{A, C, G, T\}\): base 4, chaves curtas.
  • Processamento de imagens: O histograma de uma imagem é a primeira etapa da contagem; a equalização de histograma usa exatamente as somas acumuladas de \(C\).
C5 Bucket sort e seleção em tempo linear OpcionalCormen, seção 8.4 e capítulo 9

Objetivos

  • Ordenar dados uniformes em \([0, 1)\) em tempo esperado linear com baldes
  • Achar o \(i\)-ésimo menor sem ordenar, em tempo esperado \(\Theta(n)\)
  • Conhecer a seleção determinística linear (mediana das medianas)
  • Medir \(\approx 3{,}39\,n\) comparações para a mediana

Experimento

A seleção da mediana faz cerca de 3,39 n comparações? E o bucket sort depende da distribuição?

A seleção da mediana fica em \(\approx 3{,}39\,n\) comparações em média: linear, sem ordenar. O bucket sort é linear se a hipótese de uniformidade vale; com todos os dados num balde, volta a ser quadrático. Algoritmos de caso médio dependem da distribuição da entrada; aleatorizados, só da sorte.

Conexões

  • Estatística e dados: Mediana, quartis e percentis (p50, p99) são seleções. Ferramentas de monitoramento calculam percentis de latência sem ordenar milhões de amostras.
  • Bancos de dados: ORDER BY ... LIMIT k e PERCENTILE_CONT usam seleção; histogramas em baldes guiam o otimizador de consultas.
  • Aprendizado de máquina: Árvores de decisão (k-d trees) dividem pela mediana de cada coordenada; a mediana é um estimador robusto contra valores extremos.
  • Tabelas hash: O bucket sort é uma tabela hash ordenada: a análise dos baldes (\(E[n_i^2]\)) é a mesma do encadeamento da aula 19.