A secretaria precisa guardar o nome e a nota de cada aluno. Uma primeira solução usa dois vetores: nomes[] e notas[]. O aluno i é representado pela dupla nomes[i] e notas[i].
Agora alguém ordena só o vetor de notas. Os nomes ficam na posição antiga, e o boletim passa a atribuir a nota de uma pessoa a outra. Ninguém trocou um dado sequer, mas a associação entre eles se perdeu.
Como representar cada aluno para que nome e nota sejam copiados, trocados e ordenados sempre juntos?
Antes da teoria: desenhe como três alunos ficariam na memória com dois vetores e depois com uma única “ficha” por aluno. Qual desenho torna impossível separar nome e nota?
Comparar com o desenho de exemplo
Com vetores paralelos, apenas o índice associa nome e nota, e qualquer reordenação parcial quebra essa ligação. Com uma struct, cada aluno é um único registro, e trocar ou ordenar move a ficha inteira.
Conceitos centrais
Uma struct define um novo tipo que agrupa campos relacionados. Cada campo tem nome e tipo próprios. Uma variável desse tipo é um registro: um bloco contínuo de memória que guarda todos os campos (o compilador pode deixar alguns bytes vazios entre eles, por questão de alinhamento).
Trecho: coloque dentro do main de um programa
struct Aluno {
char nome[50];
float nota;
}; // o ponto e vírgula é obrigatório
struct Aluno a; // variável do tipo struct Aluno
a.nota = 8.5f; // operador . acessa um campo
Tipo
struct Aluno descreve o formato do registro. Declarar o tipo não reserva memória.
Variável
a é um registro concreto, com espaço para todos os campos.
Campo
nome e nota são acessados com a.nome e a.nota.
typedef: um nome mais curto
typedef cria um apelido para um tipo. Com ele, não é preciso repetir a palavra struct.
Declarações: copie para um programa que tenha main
typedef struct {
char nome[50];
float nota;
} Aluno;
Aluno a = {"Ana", 8.5f}; // inicialização posicional
Aluno b = {.nota = 7.0f, .nome = "Bruno"}; // inicialização designada (C99)
Aluno c = {0}; // todos os campos zerados
Na inicialização posicional, os valores seguem a ordem dos campos. Na designada, você nomeia o campo e a ordem deixa de importar. Os campos omitidos recebem zero.
Ideia-chave
Uma struct é um valor, como um int. A atribuição b = a copia todos os campos, inclusive vetores internos como nome. Depois da cópia, alterar b não altera a.
Structs dentro de structs
Um campo pode ser outra struct. O acesso encadeia operadores ., da variável mais externa para a mais interna.
Trecho: coloque dentro do main de um programa
struct Data { int dia, mes, ano; };
struct Evento { char titulo[40]; struct Data quando; };
struct Evento prova = {"Prova 1", {14, 10, 2026}};
prova.quando.mes = 11; // evento → data → mês
Aluno turma[3] = {
{"Ana", 8.5f},
{"Bruno", 6.0f},
{"Carla", 9.0f}
};
for (int i = 0; i < 3; i++) {
if (turma[i].nota >= 7.0f) {
printf("%s\n", turma[i].nome);
}
}
Leia turma[i].nota da esquerda para a direita: primeiro escolha o registro pelo índice e depois o campo. Com nota mínima 7, a saída contém Ana e Carla.
A cópia é independente
Trecho: coloque dentro do main de um programa
Aluno copia = turma[0]; // copia nome e nota
copia.nota = 10.0f; // altera somente a cópia
printf("copia: %s %.1f\n", copia.nome, copia.nota);
printf("turma[0]: %s %.1f\n", turma[0].nome, turma[0].nota);
Saída: copia: Ana 10.0 e turma[0]: Ana 8.5
A atribuição criou um segundo registro com os mesmos valores, inclusive uma cópia própria do vetor nome. Por isso, mudar a nota da cópia não afeta turma[0].
Armadilhas frequentes
copia.nome = "Beatriz"; não compila, porque não é possível atribuir a um vetor. Use strcpy(copia.nome, "Beatriz");, de string.h, garantindo que o texto caiba no vetor.
if (a == b) não compila para structs. Compare campo a campo.
scanf("%s", a.nome) pode escrever além do vetor. Limite a leitura: scanf("%49s", a.nome). Lembre que %s para no primeiro espaço (lê só “Ana” de “Ana Maria”) e que o retorno de scanf deve ser verificado.
Se a struct tiver um campo ponteiro, a cópia duplica o endereço, não o conteúdo apontado. As duas cópias passam a compartilhar o mesmo dado.
Struct e funções
Em C, todo argumento é passado por valor. Passar uma struct copia o registro inteiro para a função. Para alterar o original, passe o endereço e use ->. A aula de ponteiros detalha esse mecanismo.
Há uma exceção aparente: um vetor passado para uma função não é copiado. A função recebe o endereço do primeiro elemento e trabalha sobre os dados originais. Por isso, relatorio(turma, 3), no exercício 3, não copia a turma.
Funções: copie para um programa que tenha main
void bonus_errado(Aluno a, float bonus) {
a.nota += bonus; // altera apenas a cópia local
}
void aplicar_bonus(Aluno *a, float bonus) {
a->nota += bonus; // a->nota equivale a (*a).nota
if (a->nota > 10.0f) a->nota = 10.0f;
}
// Chamada: aplicar_bonus(&turma[1], 1.0f); Bruno passa de 6.0 para 7.0
Boa prática
Para structs grandes que a função só lê, passe const Aluno *. Você evita copiar dezenas de bytes a cada chamada, e o compilador impede alterações acidentais.
Comparar registros: crie a sua regra
Com inteiros, “estar em ordem” é óbvio: 3 vem antes de 7. Com registros, não é. Um aluno vem antes de outro por causa da nota, do nome ou da matrícula? E se as notas forem iguais? O C não sabe responder, porque if (a < b) nem compila para structs. Quem decide a ordem é você, e a forma mais organizada de decidir é escrever essa regra como uma função.
Vamos montar o ranking da turma com esta regra:
Critério principal: quem tem a maior nota aparece primeiro.
Desempate: com notas iguais, aparece primeiro quem vem antes em ordem alfabética.
A função recebe dois alunos e responde a uma única pergunta: “x deve aparecer antes de y na lista?” Ela retorna 1 para sim e 0 para não.
Funções: copie para um programa que tenha main
#include <string.h>
// Retorna 1 se x deve aparecer antes de y na lista; 0 caso contrário.
// Regra: maior nota primeiro. Em empate de nota, ordem alfabética do nome.
int vem_antes(const Aluno *x, const Aluno *y) {
if (x->nota != y->nota) { // 1) as notas decidem?
return x->nota > y->nota; // sim: maior nota vem antes
}
return strcmp(x->nome, y->nome) < 0; // 2) empate: o nome decide
}
Lendo a função linha a linha
const Aluno *
A função recebe o endereço de cada aluno, sem copiar o registro inteiro, que tem dezenas de bytes. (Curiosidade: sizeof(Aluno) costuma valer 56 em um compilador x64 comum, ou seja, 50 do nome, 2 bytes vazios de alinhamento e 4 da nota. O valor exato depende da plataforma.) O const garante que ela só lê e não altera nenhum dado.
->
Como x é um ponteiro, os campos são acessados com x->nota, que é o mesmo que (*x).nota.
Linha 1
Se as notas são diferentes, elas já decidem a ordem. Em C, x->nota > y->nota é uma expressão que vale 1 (verdadeiro) ou 0 (falso), e por isso pode ser retornada diretamente. Comparar float com != funciona bem para notas digitadas, como 8.5. Notas resultantes de contas podem ter pequenos erros de arredondamento (0.1 + 0.2 não dá exatamente 0.3), e aí duas notas “iguais” podem não empatar.
Linha 2
Só é executada se as notas forem iguais. strcmp(a, b) retorna um número negativo se a vem antes de b, zero se forem iguais e positivo se vier depois. Por isso, o teste é < 0. Atenção: strcmp compara os códigos dos caracteres, um a um. Isso coincide com a ordem alfabética para nomes sem acento e com a mesma convenção de maiúsculas, como os desta aula. Com acentos ou caixa misturada (“Ágata”, “ana”), a ordem pode sair diferente da do dicionário.
Testando a regra com pares de alunos
Chamada
O que decide
Retorno
vem_antes(Carla 9.0, Ana 8.5)
notas diferentes: 9.0 > 8.5
1: Carla vem antes
vem_antes(Ana 8.5, Carla 9.0)
notas diferentes: 8.5 > 9.0 é falso
0
vem_antes(Ana 8.5, Davi 8.5)
empate: strcmp("Ana", "Davi") é negativo
1: Ana vem antes
vem_antes(Davi 8.5, Ana 8.5)
empate: strcmp("Davi", "Ana") é positivo
0
Usando a regra para ordenar
A vantagem aparece aqui: o algoritmo de ordenação não precisa saber nada sobre notas ou nomes. Ele só pergunta à regra quem vem antes. O código abaixo é o insertion sort, que você verá na aula 5. Repare que a única linha que fala de alunos é a chamada a vem_antes:
Programa completo: compile e execute
#include <stdio.h>
#include <string.h>
typedef struct {
char nome[50];
float nota;
} Aluno;
// A regra apresentada acima.
int vem_antes(const Aluno *x, const Aluno *y) {
if (x->nota != y->nota) {
return x->nota > y->nota;
}
return strcmp(x->nome, y->nome) < 0;
}
void ordenar_turma(Aluno t[], int n) {
for (int i = 1; i < n; i++) {
Aluno chave = t[i];
int j = i - 1;
while (j >= 0 && vem_antes(&chave, &t[j])) { // a regra decide
t[j + 1] = t[j];
j--;
}
t[j + 1] = chave;
}
}
int main(void) {
Aluno turma[4] = {{"Davi", 8.5f}, {"Bruno", 6.0f}, {"Ana", 8.5f}, {"Carla", 9.0f}};
ordenar_turma(turma, 4);
for (int i = 0; i < 4; i++)
printf("%d. %s %.1f\n", i + 1, turma[i].nome, turma[i].nota);
return 0;
}
Saída: 1. Carla 9.0 · 2. Ana 8.5 · 3. Davi 8.5 · 4. Bruno 6.0
Davi estava antes de Ana no vetor original, mas os dois têm 8.5, e o desempate alfabético colocou Ana primeiro.
Por que separar a regra do algoritmo?
Se amanhã a secretaria pedir o ranking em ordem crescente de nota, ou por matrícula, você muda só a função vem_antes, e o algoritmo de ordenação continua igual. A função qsort da biblioteca padrão de C segue a mesma ideia: você entrega o vetor e uma função de comparação. Só que o contrato dela é diferente: a função precisa retornar um número negativo, zero ou positivo, como strcmp, e não 1 ou 0. Por isso, vem_antes não pode ser passada diretamente para qsort.
Como atacar problemas com registros
Modele: quais campos descrevem uma entidade? Quais tipos?
Identifique a chave: qual campo decide buscas e ordenações? Há critério de desempate?
Mova o registro inteiro: troque e copie a struct, nunca campos isolados.
Decida a passagem: por valor para ler structs pequenas, const T * para ler structs grandes, T * para alterar.
Exercícios progressivos
Os exercícios usam a mesma turma dos exemplos: Aluno turma[3] = {{"Ana", 8.5f}, {"Bruno", 6.0f}, {"Carla", 9.0f}};. Tente resolver cada um no papel ou no compilador antes de abrir a dica. A dica não entrega a resposta: ela aponta a pergunta certa a fazer.
1. Simulação de recuperação Básico
O professor quer simular quanto Bruno ficaria se ganhasse 2 pontos na recuperação, sem alterar a nota oficial. Ele escreveu:
O que o programa imprime? A nota oficial foi preservada? E se ele tivesse escrito Aluno *p = &turma[1]; e p->nota = p->nota + 2.0f;?
Ver uma dica
Pergunte-se: depois da primeira linha, existem um ou dois registros do Bruno na memória? Na versão com ponteiro, p guarda uma cópia do registro ou o endereço do original?
Conferir a resposta
Saída: 6.0 8.0. A nota oficial foi preservada.
Aluno simulado = turma[1]; cria um segundo registro, com cópias de todos os campos. A partir daí, simulado e turma[1] são independentes, e mudar um não afeta o outro.
Na versão com ponteiro, p guarda o endereço de turma[1]. Então p->nota é a própria nota oficial, que passaria a valer 8.0. Para simular, use cópia. Para alterar o original, use ponteiro.
2. Cadastro da papelaria Básico
Uma papelaria quer cadastrar seus produtos. Cada produto tem um código numérico, um nome de até 39 letras sem acento, um preço e a quantidade em estoque.
Declare o tipo Produto com typedef.
Crie o produto 101, “Caderno”, de R$ 15,00, com 10 unidades, usando inicialização designada.
Escolha o tipo de cada campo pelo que o dado representa. É algo que se conta? É um valor com centavos? É um texto? Para o nome, lembre que o vetor precisa de uma posição a mais para o '\0'. No printf, %.2f mostra duas casas decimais.
Conferir a resposta
Trecho: coloque dentro do main de um programa
typedef struct {
int codigo; // identificador: número inteiro
char nome[40]; // até 39 bytes de texto + '\0'
float preco; // valor com centavos
int estoque; // quantidade: se conta, então é int
} Produto;
Produto caderno = {.codigo = 101, .nome = "Caderno", .preco = 15.0f, .estoque = 10};
printf("%s (cod. %d): R$ %.2f - %d un.\n",
caderno.nome, caderno.codigo, caderno.preco, caderno.estoque);
A inicialização designada (.campo = valor) deixa claro qual valor vai para qual campo e não depende da ordem da declaração.
Para ir além: sistemas financeiros reais costumam guardar preços como int em centavos (1500 em vez de 15.00), porque float não representa todos os valores decimais com exatidão. Por exemplo, 0.1 + 0.2 não dá exatamente 0.3.
3. Relatório da turma Intermediário
Escreva a função void relatorio(const Aluno t[], int n). Ela deve listar os aprovados (nota ≥ 7) com suas notas, informar quantos foram aprovados e calcular a média da turma. Use um único laço e trate o caso de turma vazia. Para a turma de exemplo, a saída esperada é:
Aprovados:
Ana (8.5)
Carla (9.0)
Total: 2 de 3
Media da turma: 7.83
Ver uma dica
Antes de escrever o laço, liste o que você precisa ter acumulado ao final: a soma das notas e a quantidade de aprovados. Cada um vira uma variável, inicializada antes do laço. A lista de aprovados pode ser impressa durante o laço. O total e a média só podem ser impressos depois dele. O que acontece com soma / n quando n vale 0?
Conferir a resposta
Funções: copie para um programa que tenha main
void relatorio(const Aluno t[], int n) {
if (n == 0) { // evita divisão por zero
printf("Turma vazia.\n");
return;
}
float soma = 0.0f;
int aprovados = 0;
printf("Aprovados:\n");
for (int i = 0; i < n; i++) {
soma += t[i].nota; // acumula todas as notas
if (t[i].nota >= 7.0f) {
printf(" %s (%.1f)\n", t[i].nome, t[i].nota);
aprovados++;
}
}
printf("Total: %d de %d\n", aprovados, n);
printf("Media da turma: %.2f\n", soma / n);
}
Sem o teste de n == 0, a divisão 0.0f / 0 não trava o programa, mas produz um valor sem sentido, impresso como nan (“não é um número”). O laço visita cada aluno uma única vez, então o tempo cresce na mesma proporção de n, ou seja, O(n). O parâmetro const Aluno t[] documenta que a função apenas lê a turma.
Erros comuns: inicializar soma dentro do laço, o que zera a soma a cada aluno; usar > 7 em vez de >= 7, o que reprova quem tirou exatamente 7; e esquecer o caso n == 0.
4. Lançamento de nota por nome Intermediário
A secretaria digita o nome de um aluno e a nota da recuperação. Escreva int buscar(const Aluno t[], int n, const char *nome), que retorna o índice do aluno com esse nome, ou −1 se ele não existir. Em seguida, use a função para atualizar a nota de Bruno para 7.5, sem quebrar o programa caso o nome não seja encontrado.
Ver uma dica
Duas perguntas guiam a solução. Primeiro: como comparar duas strings em C? O operador == compara o quê quando aplicado a vetores de caracteres? Segundo: por que retornar o índice, e não o próprio aluno? Pense no que você precisa fazer com o resultado e em como representar “não encontrado”.
Conferir a resposta
Funções e exemplo de uso: as linhas de uso vão dentro do main
#include <string.h>
int buscar(const Aluno t[], int n, const char *nome) {
for (int i = 0; i < n; i++) {
if (strcmp(t[i].nome, nome) == 0) { // 0 significa "iguais"
return i; // achou: para na hora
}
}
return -1; // percorreu tudo e não achou
}
// Uso:
int i = buscar(turma, 3, "Bruno");
if (i != -1) {
turma[i].nota = 7.5f;
} else {
printf("Aluno nao encontrado.\n");
}
t[i].nome == nome compila, mas compara endereços, e não o texto. Quase sempre resulta em “diferente”. Para comparar o conteúdo, use strcmp.
Retornar o índice permite alterar o registro original em turma[i]. Se a função retornasse um Aluno, devolveria uma cópia, e alterar essa cópia não mudaria a turma. Além disso, −1 nunca é um índice válido, por isso funciona como sinal de “não encontrado”.
A busca é linear, O(n). Ela diferencia maiúsculas de minúsculas: "bruno" não encontra "Bruno".
5. Quem é o mais velho? Avançado
O cadastro agora guarda a data de nascimento, usando uma struct dentro de outra:
Declarações: copie para um programa que tenha main
Escreva int data_antes(Data a, Data b), que retorna 1 se a é anterior a b.
Escreva int mais_velho(const Pessoa p[], int n), que retorna o índice da pessoa mais velha. Em empate, fica a primeira da lista. Pré-condição: n >= 1 (a lista não está vazia).
Um colega escreveu return a.ano < b.ano || a.mes < b.mes || a.dia < b.dia;. Encontre duas datas em que essa versão erra.
Ver uma dica
Pense em como você compara duas datas de cabeça. Você olha o ano primeiro. O mês só importa se os anos forem iguais, e o dia, só se ano e mês forem iguais. É a mesma estrutura de vem_antes: critério principal, depois desempate. Para o item 3, teste a expressão do colega com uma data a de um ano posterior ao de b. Algum dos três testes ligados por || pode dar verdadeiro mesmo assim?
Conferir a resposta
Funções: copie para um programa que tenha main
int data_antes(Data a, Data b) {
if (a.ano != b.ano) return a.ano < b.ano; // o ano decide
if (a.mes != b.mes) return a.mes < b.mes; // mesmo ano: o mês decide
return a.dia < b.dia; // mesmo mês: o dia decide
}
int mais_velho(const Pessoa p[], int n) { // exige n >= 1
int idx = 0;
for (int i = 1; i < n; i++) {
if (data_antes(p[i].nascimento, p[idx].nascimento)) {
idx = i; // nasceu antes: é mais velho
}
}
return idx;
}
Para a lista de exemplo, o resultado é o índice 2, de Carla. Davi nasceu no mesmo dia, mas data_antes retorna 0 para datas iguais, então a primeira encontrada é mantida.
O erro do colega: com a = 01/12/2001 e b = 15/01/2000, a expressão testa 2001 < 2000 (falso), 12 < 1 (falso) e 1 < 15 (verdadeiro). Ela conclui que 2001 vem antes de 2000. Cada campo só deve ser consultado quando os campos mais importantes empatam.
Alternativa: converta a data em um único número, ano * 10000 + mes * 100 + dia. Assim, 02/07/2003 vira 20030702, e basta comparar inteiros. O desafio hacker usa a mesma ideia com horários.
./desafio --nivel=hacker
Desafio hacker: o laboratório com dois professores
Tipo: opcional
Dificuldade: ★★★★
Tema: modelagem, intervalos e casos de borda
Escala: n ≤ 2000 reservas
Segunda-feira, 9h40: dois professores chegam ao LAB1 com turmas inteiras, cada um com a reserva impressa na mão. O sistema de reservas da faculdade aceitou as duas. A coordenação pediu um programa que leia todas as reservas de um mesmo dia e aponte todos os conflitos antes que isso aconteça de novo.
As reservas são exportadas assim:
Declarações: copie para um programa que tenha main
typedef struct { int hora, minuto; } Horario;
typedef struct {
char sala[10];
char responsavel[30];
Horario inicio, fim; // a sala fica livre exatamente no horário de fim
} Reserva;
Implemente:
int conflita(const Reserva *a, const Reserva *b): retorna 1 se as duas reservas são da mesma sala e seus horários se sobrepõem. Reservas que apenas se encostam, como 08:00–10:00 e 10:00–12:00, não conflitam.
int listar_conflitos(const Reserva r[], int n): imprime cada par em conflito uma única vez e retorna quantos encontrou.
Validação: dados exportados de planilhas vêm com erros. Reservas com horário impossível (hora fora de 0–23, minuto fora de 0–59, ou fim igual ou anterior ao início) devem ser reportadas como inválidas e ignoradas na busca de conflitos.
Dados de teste (já estão no vetor do programa: não é preciso ler do teclado)LAB1 Ana 08:00-10:00
LAB1 Bruno 09:30-11:00
LAB2 Carla 09:00-10:00
LAB1 Davi 10:00-12:00
LAB2 Edu 10:00-10:30
LAB1 Fabio 07:00-13:00
LAB2 Gil 14:00-13:00
Saída resumida (o programa imprime também os horários)INVALIDA LAB2: Gil (14:00-13:00)
CONFLITO LAB1: Ana x Bruno
CONFLITO LAB1: Ana x Fabio
CONFLITO LAB1: Bruno x Davi
CONFLITO LAB1: Bruno x Fabio
CONFLITO LAB1: Davi x Fabio
5 conflito(s)
Repare nas armadilhas da entrada: Ana e Davi se encostam às 10:00, e não há conflito. O mesmo vale para Carla e Edu. Carla e Bruno têm horários sobrepostos, mas estão em salas diferentes. E Fabio sobrepõe quase todo mundo no LAB1.
Dica 1: não compare horários campo a campo
Comparar hora e depois minuto com || repete o erro do exercício 5. Existe um jeito mais simples: transforme cada horário em um único número. Quantos minutos se passaram desde a meia-noite até 09:30? Depois disso, comparar horários vira comparar inteiros.
Dica 2: pense no contrário
Listar todas as formas de duas reservas se sobreporem é confuso: uma pode conter a outra, começar no meio dela, terminar no meio dela… É muito mais fácil descrever quando elas não se sobrepõem. Uma termina antes (ou exatamente quando) a outra começa, ou o contrário. Escreva essa condição e depois negue-a.
Dica 3: cada par uma única vez
Se o laço externo percorre i e o interno também percorre todos os j, cada conflito aparece duas vezes (Ana x Bruno e Bruno x Ana), e ainda compara cada reserva com ela mesma. Por onde o laço interno deve começar? Para comparar as salas, lembre-se do exercício 4.
Solução comentada
Passo 1: horário vira número. Com minutos(h) = h.hora * 60 + h.minuto, 09:30 vira 570 e 10:00 vira 600. Essa conversão elimina toda a lógica de “se a hora empatar, olhe o minuto”.
Passo 2: a condição de sobreposição. Sejam A = [a₁, a₂) e B = [b₁, b₂). A notação [a₁, a₂) quer dizer “de a₁ até logo antes de a₂”: inclui o início e exclui o fim, porque a sala fica livre exatamente no horário de fim. Eles não se sobrepõem quando a₂ <= b₁ (A termina antes de B começar) ou b₂ <= a₁ (B termina antes de A começar). Para obter a condição de conflito, negamos essa condição com a lei de De Morgan: “não (X ou Y)” é o mesmo que “(não X) e (não Y)”. Negar a₂ <= b₁ dá a₂ > b₁, ou seja, b₁ < a₂. Negar b₂ <= a₁ dá b₂ > a₁, ou seja, a₁ < b₂. Juntando com “e”:
conflito ⇔ a₁ < b₂ && b₁ < a₂
Essas duas comparações cobrem todos os casos: A contém B, B contém A, um começa no meio do outro, ou os dois são iguais. O uso de < estrito garante que reservas que só se encostam não sejam conflito: com 08:00–10:00 e 10:00–12:00, temos 600 < 600, que é falso.
Situação
Exemplo
a₁ < b₂
b₁ < a₂
Conflito?
B começa no meio de A
Ana 08–10, Bruno 09:30–11
sim
sim
sim
A contém B
Fabio 07–13, Davi 10–12
sim
sim
sim
Encostam
Ana 08–10, Davi 10–12
sim
não
não
Separados
08–09 e 14–15
sim
não
não
Programa completo: compile e execute
#include <stdio.h>
#include <string.h>
typedef struct { int hora, minuto; } Horario;
typedef struct {
char sala[10];
char responsavel[30];
Horario inicio, fim;
} Reserva;
int minutos(Horario h) {
return h.hora * 60 + h.minuto;
}
int horario_valido(Horario h) {
return h.hora >= 0 && h.hora <= 23 && h.minuto >= 0 && h.minuto <= 59;
}
int reserva_valida(const Reserva *r) {
return horario_valido(r->inicio) && horario_valido(r->fim)
&& minutos(r->inicio) < minutos(r->fim);
}
int conflita(const Reserva *a, const Reserva *b) {
if (strcmp(a->sala, b->sala) != 0) return 0; // salas diferentes
return minutos(a->inicio) < minutos(b->fim) // a1 < b2
&& minutos(b->inicio) < minutos(a->fim); // b1 < a2
}
void imprimir_reserva(const Reserva *r) {
printf("%s (%02d:%02d-%02d:%02d)", r->responsavel,
r->inicio.hora, r->inicio.minuto, r->fim.hora, r->fim.minuto);
}
int listar_conflitos(const Reserva r[], int n) {
for (int i = 0; i < n; i++) { // 1) relata inválidas
if (!reserva_valida(&r[i])) {
printf("INVALIDA %s: ", r[i].sala);
imprimir_reserva(&r[i]);
printf("\n");
}
}
int total = 0;
for (int i = 0; i < n; i++) { // 2) testa cada par uma vez
if (!reserva_valida(&r[i])) continue;
for (int j = i + 1; j < n; j++) { // j começa em i + 1
if (!reserva_valida(&r[j])) continue;
if (conflita(&r[i], &r[j])) {
printf("CONFLITO %s: ", r[i].sala);
imprimir_reserva(&r[i]);
printf(" x ");
imprimir_reserva(&r[j]);
printf("\n");
total++;
}
}
}
printf("%d conflito(s)\n", total);
return total;
}
int main(void) {
Reserva r[] = {
{"LAB1", "Ana", { 8, 0}, {10, 0}},
{"LAB1", "Bruno", { 9, 30}, {11, 0}},
{"LAB2", "Carla", { 9, 0}, {10, 0}},
{"LAB1", "Davi", {10, 0}, {12, 0}},
{"LAB2", "Edu", {10, 0}, {10, 30}},
{"LAB1", "Fabio", { 7, 0}, {13, 0}},
{"LAB2", "Gil", {14, 0}, {13, 0}}
};
int n = sizeof r / sizeof r[0]; // quantidade de elementos do vetor
listar_conflitos(r, n);
return 0;
}
A saída real inclui os horários. A primeira linha de conflito, por exemplo, é CONFLITO LAB1: Ana (08:00-10:00) x Bruno (09:30-11:00).
Por que j = i + 1? Com n reservas, existem n(n−1)/2 pares distintos. Começar o laço interno em i + 1 visita cada par exatamente uma vez e nunca compara uma reserva com ela mesma. Para n = 2000, são cerca de 2 milhões de comparações, o que leva uma fração de segundo. O tempo cresce com o quadrado do número de reservas, ou seja, O(n²): o dobro de reservas dá cerca de 4 vezes mais pares. A memória extra não depende de n, ou seja, O(1).
Por que validar? Uma reserva de 14:00 a 13:00 tem duração negativa. Sem validação, ela pode gerar conflitos falsos ou esconder conflitos reais. Tratar entrada ruim é parte do problema, e não um detalhe.
Como foi testado: além do exemplo, a função conflita foi comparada com uma versão de força bruta que verifica, minuto a minuto, se existe algum instante ocupado pelas duas reservas. As duas versões concordaram em mais de 250 mil pares de intervalos, incluindo casos que se encostam, que estão contidos um no outro e que estão separados. Também se verificou que conflita(a, b) == conflita(b, a).
Um limite da validação: como a hora vai de 0 a 23, uma reserva que termina à meia-noite (22:00–24:00) é rejeitada. Para aceitá-la, permita 24:00 só como horário de fim.
Indo além 1: a semana inteira. Para tratar reservas de vários dias, acrescente um campo int dia; (0 = segunda, 1 = terça…) e compare o dia junto com a sala: reservas em dias diferentes nunca conflitam. Sem esse campo, LAB1 de segunda das 8 às 10 e LAB1 de terça das 9 às 11 seriam acusadas como conflito.
Indo além 2: muitas reservas. Com dezenas de milhares de reservas, comparar todos os pares começa a pesar. A técnica profissional é ordenar as reservas por dia, sala e horário de início. Depois disso, cada reserva só precisa ser comparada com as seguintes que começam antes de ela terminar. Isso acelera muito quando há poucos conflitos. Se quase todas se sobrepõem, porém, existem quase n(n−1)/2 pares para imprimir, e nenhum truque evita esse custo de saída. Você vai ver algoritmos de ordenação a partir da aula 4.
Revisão rápida
Tipo, variável e campo: struct Aluno, a e a.nota.
A atribuição copia a struct inteira. == não funciona com structs.
. acessa campos de uma variável. -> acessa campos por um ponteiro.
Trocar registros inteiros mantém os dados associados. Esse é o motivo de usar struct.
Para comparar registros, escreva uma regra: critério principal primeiro, desempate depois.
Dados compostos, como horários e datas, ficam mais fáceis de comparar quando convertidos em um único número.