| Universidade Federal do Rio Grande do Sul Instituto de Informática |
Prof. Dennis G. Balreira Semestre 2026/2 — Turma C |
PilhaEnc — o usuário chama push/pop sem saber que internamente usa lista encadeada.
malloc/free é essencial para TADs encadeados? O que é memory leak?malloc, pois o tamanho total não é conhecido em tempo de compilação.
Sem alocação dinâmica, seria necessário um vetor com tamanho máximo fixo, desperdiçando memória ou limitando o TAD.free para cada nodo removido, a memória permanece ocupada até o fim do processo — pode esgotar a memória em programas de longa duração.
inserirInicio — apenas atualiza o ponteiro ini, independente do tamanho.fim — exige percorrer todos os nodos.
ListaEnc) de uma lista simplesmente encadeada.dado (conteúdo, ex: Produto) e prox (ponteiro para o próximo; NULL no último).ListaEnc): struct que representa a lista como um todo; contém apenas ini, ponteiro para o primeiro nodo (NULL se vazia).inserirInicio recebe ListaEnc *lista? O que ocorreria com passagem por valor?ini do descritor para apontar para o novo nodo.
Se recebesse por valor (ListaEnc lista), receberia uma cópia do descritor: a alteração de lista.ini ficaria nessa cópia local e seria descartada ao retornar — a lista original não seria modificada.ListaEnc *lista, lista->ini = novo modifica o descritor original.
lista->ini == NULL); se sim, retornar.Nodo *tmp = lista->ini;lista->ini = lista->ini->prox;free(tmp);tmp antes de atualizar ini é essencial; caso contrário o ponteiro para o nodo é perdido e a memória vaza.
ant.prox (para o seguinte) e ant (para o anterior).anterior — basta acessar nodo->ant diretamente, sem varredura adicional.
alvo o nodo a remover, com vizinhos alvo->ant e alvo->prox:alvo->ant->prox = alvo->prox; — o anterior aponta para o próximo de alvo.alvo->prox->ant = alvo->ant; — o próximo aponta de volta para o anterior de alvo.free(alvo);ini do descritor; se é o último, ajustar fim.
prox do último nodo aponta de volta para o primeiro (em vez de NULL), formando um ciclo.NULL, compara-se o nodo atual com o nodo inicial (sentinela): a varredura termina quando atual == primeiro (ao retornar ao ponto de partida).i (de 1 a n−1): guarda chave = vetor[i] e desloca para a direita os elementos do segmento ordenado que sejam maiores que chave, depois insere chave na posição correta.PilhaEnc e onde ocorrem push/pop.inicializaPilha (topo=NULL), estaVaziaPilha, push (insere no topo), pop (remove e retorna o topo).return desempilha, restaurando o contexto anterior.fim.enqueue (inserção) ocorre no fim; dequeue (remoção) ocorre no início.fim torna o enqueue O(1): sem ele, seria preciso percorrer toda a lista para chegar ao último nodo (O(n)) a cada inserção.
Com ini e fim, ambas as operações são O(1).
esq == NULL && dir == NULL).NULL e liberar.NULL) e remover esse sucessor recursivamente (que é sempre caso 1 ou 2).
Tipos e TADs (mesmos do simulado):
typedef struct { int cod; char nome[50]; float preco; } Produto;
typedef struct str_Nodo Nodo;
struct str_Nodo { Nodo *prox; Produto dado; };
typedef struct { Nodo *ini; } ListaEnc;
typedef struct str_NodoPilha NodoPilha;
struct str_NodoPilha { NodoPilha *prox; Produto dado; };
typedef struct { NodoPilha *topo; } PilhaEnc;
void inicializaPilha(PilhaEnc *p); int estaVaziaPilha(PilhaEnc *p);
void push(PilhaEnc *p, Produto d); Produto pop(PilhaEnc *p);
typedef struct str_NodoFila NodoFila;
struct str_NodoFila { NodoFila *prox; Produto dado; };
typedef struct { NodoFila *ini; NodoFila *fim; } FilaEnc;
void inicializaFila(FilaEnc *f); int estaVaziaFila(FilaEnc *f);
void enqueue(FilaEnc *f, Produto d); Produto dequeue(FilaEnc *f);
typedef struct str_NodoArv NodoArv;
struct str_NodoArv { Produto dado; NodoArv *esq; NodoArv *dir; };
Produto maiorPreco(const ListaEnc *lista);
Produto maiorPreco(const ListaEnc *lista) {
Nodo *atual = lista->ini;
Produto maior = atual->dado; /* inicia com o primeiro */
atual = atual->prox;
while (atual != NULL) {
if (atual->dado.preco > maior.preco)
maior = atual->dado;
atual = atual->prox;
}
return maior;
}
Inicializar com o primeiro (não com zero) garante que qualquer preço válido seja reconhecido.
float somarPrecosImpares(const ListaEnc *lista);
float somarPrecosImpares(const ListaEnc *lista) {
float soma = 0.0;
Nodo *atual = lista->ini;
while (atual != NULL) {
if (atual->dado.cod % 2 != 0)
soma += atual->dado.preco;
atual = atual->prox;
}
return soma;
}
void removerMenoresQuePreco(ListaEnc *lista, float p);
void removerMenoresQuePreco(ListaEnc *lista, float p) {
while (lista->ini != NULL && lista->ini->dado.preco < p) {
Nodo *tmp = lista->ini;
lista->ini = lista->ini->prox;
free(tmp);
}
if (lista->ini == NULL) return;
Nodo *ant = lista->ini;
while (ant->prox != NULL) {
if (ant->prox->dado.preco < p) {
Nodo *tmp = ant->prox;
ant->prox = tmp->prox;
free(tmp);
/* NAO avanca ant */
} else {
ant = ant->prox;
}
}
}
void concatenarListas(ListaEnc *a, const ListaEnc *b);
void concatenarListas(ListaEnc *a, const ListaEnc *b) {
if (b->ini == NULL) return;
if (a->ini == NULL) {
a->ini = b->ini;
return;
}
Nodo *atual = a->ini;
while (atual->prox != NULL)
atual = atual->prox;
atual->prox = b->ini;
}
Nenhum nodo é criado: apenas o prox do último de a é atualizado para apontar para b->ini.
void inverterLista(ListaEnc *lista);
void inverterLista(ListaEnc *lista) {
Nodo *ant = NULL;
Nodo *atual = lista->ini;
while (atual != NULL) {
Nodo *prox = atual->prox; /* 1. guarda o proximo */
atual->prox = ant; /* 2. inverte ponteiro */
ant = atual; /* 3. avanca ant */
atual = prox; /* 4. avanca atual */
}
lista->ini = ant;
}
[1,2,3] vira [3,2,1]. Cada passo inverte apenas um ponteiro; nenhum nodo é alocado.
void moverPrimeiroParaFim(ListaEnc *lista);
void moverPrimeiroParaFim(ListaEnc *lista) {
if (lista->ini == NULL) return;
if (lista->ini->prox == NULL) return; /* unitaria */
Nodo *primeiro = lista->ini;
lista->ini = primeiro->prox; /* remove o primeiro */
primeiro->prox = NULL;
Nodo *atual = lista->ini;
while (atual->prox != NULL)
atual = atual->prox;
atual->prox = primeiro; /* liga ao fim */
}
void inserirAntesDeCod(ListaEnc *lista, Produto p, int cod);
void inserirAntesDeCod(ListaEnc *lista, Produto p, int cod) {
Nodo *novo = (Nodo*) malloc(sizeof(Nodo));
novo->dado = p;
novo->prox = NULL;
/* lista vazia ou o primeiro ja tem o cod */
if (lista->ini == NULL || lista->ini->dado.cod == cod) {
novo->prox = lista->ini;
lista->ini = novo;
return;
}
/* percorre: para quando ant->prox tem o cod ou chegou ao fim */
Nodo *ant = lista->ini;
while (ant->prox != NULL && ant->prox->dado.cod != cod)
ant = ant->prox;
/* insere apos ant (antes de ant->prox, seja ele o cod ou NULL) */
novo->prox = ant->prox;
ant->prox = novo;
}
Se cod não existe, o while para com ant->prox == NULL e o novo é inserido no fim (novo->prox = NULL).
float segundoMaiorPreco(const ListaEnc *lista);
float segundoMaiorPreco(const ListaEnc *lista) {
float maior = -1.0;
float segundo = -1.0;
Nodo *atual = lista->ini;
while (atual != NULL) {
if (atual->dado.preco > maior) {
segundo = maior;
maior = atual->dado.preco;
} else if (atual->dado.preco < maior && atual->dado.preco > segundo) {
segundo = atual->dado.preco;
}
atual = atual->prox;
}
return segundo;
}
Uma só passagem O(n). Retorna -1.0 se todos os preços forem iguais ou a lista tiver um único preço distinto.
Produto maiorPrecoFila(FilaEnc *fila);
— maior preço da fila sem alterar a ordem.
Produto maiorPrecoFila(FilaEnc *fila) {
FilaEnc temp;
Produto v, maior;
int n = 0;
inicializaFila(&temp);
while (!estaVaziaFila(fila)) {
v = dequeue(fila);
if (n == 0 || v.preco > maior.preco)
maior = v;
enqueue(&temp, v);
n++;
}
while (!estaVaziaFila(&temp))
enqueue(fila, dequeue(&temp));
return maior;
}
Dequeue todos para temp (processando), depois enqueue de volta. A ordem é preservada pois temp também é FIFO.
Produto maiorPrecoPilha(PilhaEnc *pilha);
— maior preço da pilha sem alterar a ordem.
Produto maiorPrecoPilha(PilhaEnc *pilha) {
PilhaEnc temp;
Produto v, maior;
int n = 0;
inicializaPilha(&temp);
while (!estaVaziaPilha(pilha)) {
v = pop(pilha);
if (n == 0 || v.preco > maior.preco)
maior = v;
push(&temp, v);
n++;
}
/* pop de temp e push de volta = duas reversoes = ordem original */
while (!estaVaziaPilha(&temp))
push(pilha, pop(&temp));
return maior;
}
Pop da pilha inverte a ordem em temp. Pop de temp de volta para pilha inverte novamente: ordem restaurada.
void removerDaFilaPorCod(FilaEnc *fila, int cod);
— remove o primeiro produto com cod da fila, preservando os demais.
void removerDaFilaPorCod(FilaEnc *fila, int cod) {
FilaEnc temp;
Produto v;
int removido = 0;
inicializaFila(&temp);
while (!estaVaziaFila(fila)) {
v = dequeue(fila);
if (!removido && v.cod == cod) {
removido = 1; /* descarta este elemento */
} else {
enqueue(&temp, v);
}
}
while (!estaVaziaFila(&temp))
enqueue(fila, dequeue(&temp));
}
void removerDaPilhaPorCod(PilhaEnc *pilha, int cod);
— remove o primeiro elemento (do topo) com cod, preservando a ordem.
void removerDaPilhaPorCod(PilhaEnc *pilha, int cod) {
PilhaEnc temp;
Produto v;
int removido = 0;
inicializaPilha(&temp);
while (!estaVaziaPilha(pilha)) {
v = pop(pilha);
if (!removido && v.cod == cod) {
removido = 1; /* descarta o primeiro encontrado */
} else {
push(&temp, v);
}
}
while (!estaVaziaPilha(&temp))
push(pilha, pop(&temp));
}
O double-reversal (pop para temp, pop de volta para pilha) restaura a ordem original. A flag removido garante que apenas o primeiro com cod é descartado.
void separarParImpar(FilaEnc *fila, FilaEnc *pares, FilaEnc *impares);
— separa a fila em duas por paridade do cod. A fila original é consumida.
void separarParImpar(FilaEnc *fila, FilaEnc *pares, FilaEnc *impares) {
Produto v;
inicializaFila(pares);
inicializaFila(impares);
while (!estaVaziaFila(fila)) {
v = dequeue(fila);
if (v.cod % 2 == 0)
enqueue(pares, v);
else
enqueue(impares, v);
}
}
float somarPrecosImparesPreservando(FilaEnc *fila);
— soma preços dos produtos com cod ímpar, preservando a fila.
float somarPrecosImparesPreservando(FilaEnc *fila) {
FilaEnc temp;
Produto v;
float soma = 0.0;
inicializaFila(&temp);
while (!estaVaziaFila(fila)) {
v = dequeue(fila);
if (v.cod % 2 != 0)
soma += v.preco;
enqueue(&temp, v);
}
while (!estaVaziaFila(&temp))
enqueue(fila, dequeue(&temp));
return soma;
}
int temConsecutivosIguais(FilaEnc *fila);
— retorna 1 se existem dois produtos consecutivos com o mesmo cod, preservando a fila.
int temConsecutivosIguais(FilaEnc *fila) {
FilaEnc temp;
Produto v, ant;
int tem = 0, primeiro = 1;
inicializaFila(&temp);
while (!estaVaziaFila(fila)) {
v = dequeue(fila);
if (!primeiro && v.cod == ant.cod)
tem = 1;
ant = v;
primeiro = 0;
enqueue(&temp, v);
}
while (!estaVaziaFila(&temp))
enqueue(fila, dequeue(&temp));
return tem;
}
int mesmasElementos(PilhaEnc *pilha, FilaEnc *fila);
— retorna 1 se pilha e fila têm mesma quantidade e mesmos cod na mesma ordem (topo ↔ frente). Ambas preservadas.
int mesmasElementos(PilhaEnc *pilha, FilaEnc *fila) {
PilhaEnc tempP;
FilaEnc tempF;
Produto vP, vF;
int iguais = 1;
inicializaPilha(&tempP);
inicializaFila(&tempF);
while (!estaVaziaPilha(pilha) && !estaVaziaFila(fila)) {
vP = pop(pilha);
vF = dequeue(fila);
if (vP.cod != vF.cod) iguais = 0;
push(&tempP, vP);
enqueue(&tempF, vF);
}
/* se uma estrutura ainda tem elementos, tamanhos sao diferentes */
if (!estaVaziaPilha(pilha) || !estaVaziaFila(fila)) iguais = 0;
/* restaurar pilha (double-reversal) */
while (!estaVaziaPilha(&tempP))
push(pilha, pop(&tempP));
/* restaurar fila */
while (!estaVaziaFila(&tempF))
enqueue(fila, dequeue(&tempF));
return iguais;
}
Pop da pilha e dequeue da fila percorrem ambas na mesma ordem, permitindo comparação direta. O double-reversal da pilha restaura a ordem original.
int abpTodosPares(const NodoArv *raiz);
int abpTodosPares(const NodoArv *raiz) {
if (raiz == NULL) return 1;
if (raiz->dado.cod % 2 != 0) return 0;
if (abpTodosPares(raiz->esq) == 0) return 0;
return abpTodosPares(raiz->dir);
}
NULL → 1 (vazio vacuamente satisfaz). Retorno antecipado ao encontrar o primeiro ímpar evita percurso desnecessário.
int abpContarComUmFilho(const NodoArv *raiz);
int abpContarComUmFilho(const NodoArv *raiz) {
int cont;
if (raiz == NULL) return 0;
cont = abpContarComUmFilho(raiz->esq)
+ abpContarComUmFilho(raiz->dir);
if (raiz->esq == NULL && raiz->dir != NULL) cont++;
if (raiz->esq != NULL && raiz->dir == NULL) cont++;
return cont;
}
Folhas (sem filhos) e nodos com dois filhos não são contados.
int abpContarMenoresQue(const NodoArv *raiz, int x);
int abpContarMenoresQue(const NodoArv *raiz, int x) {
if (raiz == NULL) return 0;
if (raiz->dado.cod >= x)
return abpContarMenoresQue(raiz->esq, x);
return 1
+ abpContarMenoresQue(raiz->esq, x)
+ abpContarMenoresQue(raiz->dir, x);
}
Quando cod >= x, toda a subárvore direita também é >= x (propriedade ABP) — podada.
int abpMaiorCod(const NodoArv *raiz);
int abpMaiorCod(const NodoArv *raiz) {
if (raiz->dir == NULL)
return raiz->dado.cod;
return abpMaiorCod(raiz->dir);
}
O maior elemento está sempre no extremo direito da ABP. Complexidade O(h), não O(n).
void abpImprimirIntervalo(const NodoArv *raiz, int minCod, int maxCod);
void abpImprimirIntervalo(const NodoArv *raiz, int minCod, int maxCod) {
if (raiz == NULL) return;
if (raiz->dado.cod > minCod)
abpImprimirIntervalo(raiz->esq, minCod, maxCod);
if (raiz->dado.cod >= minCod && raiz->dado.cod <= maxCod)
printf("%d - %s\n", raiz->dado.cod, raiz->dado.nome);
if (raiz->dado.cod < maxCod)
abpImprimirIntervalo(raiz->dir, minCod, maxCod);
}
In-ordem garante impressão crescente. Poda ambos os lados fora do intervalo. Exemplo: árvore 10→{5,20} com 5→{3,7}, [5,15]: imprime 5, 7, 10.
int abpContarMaioresQue(const NodoArv *raiz, int x);
int abpContarMaioresQue(const NodoArv *raiz, int x) {
if (raiz == NULL) return 0;
if (raiz->dado.cod <= x)
return abpContarMaioresQue(raiz->dir, x);
return 1
+ abpContarMaioresQue(raiz->esq, x)
+ abpContarMaioresQue(raiz->dir, x);
}
Simétrico de abpContarMenoresQue: quando cod <= x, toda a subárvore esquerda também é <= x (podada).
int abpExisteNoIntervalo(const NodoArv *raiz, int minCod, int maxCod);
int abpExisteNoIntervalo(const NodoArv *raiz, int minCod, int maxCod) {
if (raiz == NULL) return 0;
if (raiz->dado.cod >= minCod && raiz->dado.cod <= maxCod) return 1;
if (raiz->dado.cod < minCod) /* muito pequeno: busca na direita */
return abpExisteNoIntervalo(raiz->dir, minCod, maxCod);
return abpExisteNoIntervalo(raiz->esq, minCod, maxCod); /* muito grande */
}
Retorno antecipado ao encontrar o primeiro elemento no intervalo. Poda um lado por vez usando a propriedade da ABP: O(h) no melhor caso.
int abpSomaCods(const NodoArv *raiz);
int abpSomaCods(const NodoArv *raiz) {
if (raiz == NULL) return 0;
return raiz->dado.cod
+ abpSomaCods(raiz->esq)
+ abpSomaCods(raiz->dir);
}
Percorre toda a árvore em O(n). Mesma estrutura de abpContarNodos, somando cod em vez de 1 por nodo.