Caderno de estudo
Algoritmos 2
Uma revisão para entender o código, acompanhar sua execução e resolver problemas novos, inclusive os difíceis.
Começamos pela organização dos dados (struct) e pela memória (ponteiros). Em seguida, vem recursão, a ferramenta para pensar em problemas menores. Por fim, comparamos três ordenações e vemos o que cada uma ensina sobre invariantes, inversões e custo.
Ideia-chave
Caixas azuis trazem o que você precisa levar para a prova.
Armadilhas
Caixas laranja mostram os erros que mais derrubam código em C.
Exercícios em níveis
Básico, intermediário e avançado, com dica e resposta.
$ hacker
Perto do fim de cada aula há um desafio difícil e opcional, com solução comentada.
Encontrou uma palavra nova, como invariante, inversão, aliasing ou memoização? Todas estão explicadas no glossário, em uma ou duas frases.
Percurso da revisão
Struct o laboratório com dois professores
Registros, cópia, passagem para funções e regras de comparação.
Abrir aulaPonteiros o swap mágico
Endereços, aritmética de ponteiros, vetores e aliasing.
Abrir aulaFunções recursivas o cofre das somas
Caso base, pilha de chamadas, pastas, busca binária e, como aprofundamento, memoização e backtracking.
Abrir aulaBubble sort o juiz que conta trocas
Trocas entre vizinhos, inversões e parada antecipada.
Abrir aulaInsertion sort o sensor quase ordenado
Prefixo ordenado, deslocamentos e vetores quase ordenados.
Abrir aulaSelection sort o robô do armazém
Seleção do menor, mínimo de trocas e estabilidade.
Abrir aula
Método para resolver problemas de algoritmos
Programadores experientes raramente “veem” a solução de imediato. Eles seguem um roteiro. Use este em todos os exercícios e desafios:
- Entenda a entrada e a saída. Quais são os tipos, tamanhos e limites? Reescreva o enunciado com suas palavras e resolva um exemplo pequeno à mão.
- Comece pela força bruta. Qual é a solução mais simples que funciona, mesmo que lenta? Ela serve de referência para testar a solução final.
- Estime o custo e compare com o limite. Faça a conta: com n = 10⁵, n² = 10¹⁰ operações; 10¹⁰ ÷ 10⁸ por segundo ≈ 100 segundos. Já n log₂ n = 10⁵ × 17 ≈ 1,7 × 10⁶; ÷ 10⁸ ≈ 0,02 segundo. Como regra de bolso, um computador comum faz da ordem de 10⁸ operações simples por segundo. Com n = 10⁵, um algoritmo que cresce como n² faz da ordem de 10¹⁰ operações e não passa. Um que cresce como n log₂ n faz cerca de 1,7 × 10⁶ e passa com folga. É uma estimativa: memória, entrada e saída e o custo real de cada operação mudam o resultado.
- Procure estrutura. Há um invariante (algo que continua verdadeiro a cada passo)? O problema se divide em subproblemas menores? A entrada já vem quase ordenada? Existe uma medida, como inversões (pares fora de ordem) ou ciclos, que conta o trabalho?
- Escreva e argumente. Para laços, use invariantes. Para recursão, use caso base mais indução. Explicar por que funciona ajuda a encontrar os casos que ainda precisam ser testados.
- Teste as bordas. Vetor vazio, um elemento, todos iguais, já ordenado, ordem inversa, valores negativos e limites do tipo: um
intde 32 bits vai só até cerca de 2,1 bilhões, então somas e contagens maiores pedemlong long. Compare com a força bruta em entradas aleatórias.
- O que fica garantido depois de cada iteração? (invariante)
- Se eu soubesse a resposta para um problema menor, conseguiria montar a do maior? (recursão)
- Estou recalculando alguma coisa? (memoização: guardar resultados já calculados para não refazer)
Complexidade em uma página
A notação O descreve como o custo cresce com n, ignorando constantes. Na prática, ela responde: se n ficar 1000 vezes maior, um algoritmo O(n) fica cerca de 1000 vezes mais lento, e um O(n²), cerca de 1 milhão de vezes.
O símbolo log₂ n é quantas vezes dá para dividir n ao meio até chegar a 1. Para 1 000 000, são uns 20 cortes, por isso log₂ n cresce muito devagar.
| Ordem | Nome | Exemplo nesta revisão | n viável (estimativa) |
|---|---|---|---|
| O(1) | constante | acessar v[i], p->nota | o custo não depende de n |
| O(log n) | logarítmica | busca binária | muito grande: o limite vem da memória, não do tempo |
| O(n) | linear | média de notas, contar ciclos (desafio do selection sort) | até ~10⁸ |
| O(n log n) | linearítmica | merge sort, contar inversões (desafio do bubble sort) | 10⁶ (10⁷ já passa do orçamento) |
| O(n²) | quadrática | bubble, insertion, selection | até ~10⁴ |
| O(2ⁿ) | exponencial | gerar todos os subconjuntos | ≈ 25 (imprimir cada subconjunto custa ainda mais) |
| ≈ O(1,6ⁿ) | exponencial, um pouco mais lenta | Fibonacci ingênuo | ≈ 35 a 40 |
- Laços aninhados que percorrem tudo multiplicam os custos. Laços em sequência somam, e vale o maior.
- Se cada etapa descarta metade das possibilidades e faz trabalho constante, como na busca binária, são cerca de log₂ n etapas. Se resolve as duas metades e ainda percorre tudo para juntar, como o merge sort, o total cresce como n log n.
- Os números da tabela são estimativas para ter ordem de grandeza, não garantias: dependem da máquina, do compilador e dos dados.
- Melhor caso, pior caso e caso médio são perguntas diferentes. Diga sempre de qual está falando.
Como estudar
Em cada aula, leia o problema inicial e proponha uma solução antes da teoria. Nos exemplos, preveja a saída antes de executar. Nos exercícios, tente primeiro e abra a dica só quando travar. Abra a resposta só depois de ter uma resposta sua.
Os desafios hacker são opcionais e vão além da aula. Para cada um:
- Tente por 30 minutos antes de abrir qualquer dica.
- Abra uma dica por vez e tente de novo antes de passar à seguinte.
- Depois de ler a solução, reescreva-a do zero, sem consultar, e teste contra uma força bruta.
Partes marcadas com Aprofundamento também são opcionais: estude primeiro o núcleo da aula.