Pilhas
1. O que são Pilhas
Uma pilha é um Tipo Abstrato de Dados que implementa o comportamento LIFO (“Last In, First Out”), isto é, o último elemento que entrou na estrutura será o primeiro a sair. Na vida real, é como uma pilha de pratos: você empilha um em cima do outro e, na hora de retirar, pega sempre o prato que está por cima.
"Murilão, denovo mais uma estrutura? Por que tantas?"
Aqui você tem uma ótima pergunta! Trabalhar com diferentes estruturas, nos permite implementar comportamentos específicos por cada uma das estruturas. O que ganhamos com isso? Estruturas mais especializadas para realizar suas tarefas. Fica mais fácil de compreender como ela está funcionando e também nos permite manter melhor esse código.
As operações fundamentais de uma pilha são:
- push(elemento): insere um novo elemento no “topo” da pilha.
- pop() → elemento: remove e retorna o elemento que está no topo.
- top() → elemento (ou peek): consulta o elemento no topo sem removê-lo.
- isEmpty() → booleano: verifica se a pilha não contém elementos.
- size() → inteiro: retorna quantos elementos estão atualmente empilhados.
2. Por que usar uma pilha?
Vamos la, vamos ver algumas das aplicações que podemos desenvolver utilizando essa estrutura. Pessoal vale lembrar que podemos utilizar esse tipo abstrato quando for interessante utilizar este comportamento!
- Controle de fluxo em chamadas de função: a pilha de chamadas armazena cada frame de execução e desempilha quando a função retorna.
- Desfazer/refazer (undo/redo): em editores de texto ou gráficos, cada ação é empilhada para que possa ser revertida na ordem inversa.
- Avaliação de expressões e parsing: compiladores usam pilhas para converter e avaliar notações infix, prefix ou pós-fix.
- Algoritmos de backtracking: como DFS em grafos ou soluções de labirintos, empilha-se uma escolha e desempilha-se quando for preciso “voltar atrás”.
A abstração de pilha permite que você se concentre em o que precisa empilhar e desempilhar, sem expor detalhes de como a memória ou o vetor subjacente está sendo manipulado. Isso torna o código mais claro, flexível e fácil de manter.
3. Pseudocódigo das funções
Pessoal aqui vamos dar uma olhada em como são as funções que podem ser implementadas com as pilhas.
# Exemplo 1: Pilha com vetor (Array-Based Stack)
TAD PilhaArray
CONSTANTES:
MAX ← 100 # capacidade máxima da pilha
ATRIBUTOS:
dados: vetor[0..MAX-1] de Elemento
topo: inteiro # índice do próximo slot livre
MÉTODOS:
criar() -> PilhaArray
pilha.topo ← 0
RETORNAR pilha
isEmpty() -> booleano
RETORNAR (pilha.topo = 0)
isFull() -> booleano
RETORNAR (pilha.topo = MAX)
push(elem: Elemento)
SE isFull()
ESCREVER "Erro: pilha cheia"
RETORNAR
pilha.dados[pilha.topo] ← elem
pilha.topo ← pilha.topo + 1
pop() -> Elemento
SE isEmpty()
ESCREVER "Erro: pilha vazia"
RETORNAR nulo
pilha.topo ← pilha.topo - 1
RETORNAR pilha.dados[pilha.topo]
top() -> Elemento
SE isEmpty()
ESCREVER "Erro: pilha vazia"
RETORNAR nulo
RETORNAR pilha.dados[pilha.topo - 1]
size() -> inteiro
RETORNAR pilha.topo
Vamos agora analisar alguns pontos desse pseudocódigo:
- A implementação é realizada com vetor.
- Criar a pilha, inicia seu topo com o valor zero.
- A operação de
push
, se a pilha não estiver cheia, adiciona o elemento no final da pilha. - A operação de
pop
retira o elemento da pilha, o último que foi adicionado.
4. Implementação em C
Agora, vamos ver a implementação do código em C:
#include <stdio.h>
#include <stdbool.h>
#define MAX 100 // capacidade máxima da pilha
// Tipo PilhaArray armazenando inteiros
typedef struct {
int dados[MAX];
int topo; // índice do próximo slot livre
} Pilha;
// Inicializa a pilha vazia
void criarPilha(Pilha *p) {
p->topo = 0;
}
// Retorna true se a pilha estiver vazia
bool isEmpty(Pilha *p) {
return (p->topo == 0);
}
// Retorna true se a pilha estiver cheia
bool isFull(Pilha *p) {
return (p->topo == MAX);
}
// Insere elemento no topo
bool push(Pilha *p, int x) {
if (isFull(p)) {
printf("Erro: pilha cheia\n");
return false;
}
p->dados[p->topo++] = x;
return true;
}
// Remove e retorna o elemento do topo
// em *ret; retorna false em underflow
bool pop(Pilha *p, int *ret) {
if (isEmpty(p)) {
printf("Erro: pilha vazia\n");
return false;
}
*ret = p->dados[--p->topo];
return true;
}
// Consulta o topo sem remover
bool top(Pilha *p, int *ret) {
if (isEmpty(p)) {
printf("Erro: pilha vazia\n");
return false;
}
*ret = p->dados[p->topo - 1];
return true;
}
// Retorna o número de elementos na pilha
int size(Pilha *p) {
return p->topo;
}
// Exemplo de uso
int main() {
Pilha pilha;
criarPilha(&pilha);
// Empilhando valores
push(&pilha, 10);
push(&pilha, 20);
push(&pilha, 30);
printf("Tamanho após pushes: %d\n", size(&pilha)); // deve imprimir 3
// Consultando o topo
int v;
if (top(&pilha, &v))
printf("Topo atual: %d\n", v); // deve imprimir 30
// Desempilhando valores
while (!isEmpty(&pilha)) {
pop(&pilha, &v);
printf("Pop: %d | Tamanho agora: %d\n", v, size(&pilha));
}
// Tentando pop em pilha vazia
pop(&pilha, &v); // imprime "Erro: pilha vazia"
return 0;
}
Importante destacar aqui que essa pilha foi implementada para números inteiros. Contudo, podemos utilizar o mesmo princípio para construir pilhas para outros tipos de dados.
5. Exercícios
5.1: Histórico de Navegação Web
Contexto: Um navegador mantém o histórico de páginas visitadas para permitir “Voltar” e “Avançar”. Tarefa:
-
Modele o TAD
PilhaHistorico
onde cada elemento guardaurl
(texto) etimestamp
. -
Defina operações:
push(pagina)
pop() -> pagina
top() -> pagina
isEmpty() -> booleano
size() -> inteiro
-
Escreva pseudocódigo para implementar cada operação.
-
Simule: visita “A”, “B”, “C”; dá 2 cliques em “Voltar” (pop); mostra a página atual (top).
5.2: Validação de Parênteses em Expressões
Contexto: Um compilador verifica se todos os parênteses, chaves e colchetes estão corretamente balanceados. Tarefa:
-
Crie TAD
PilhaChar
. -
Escreva função
validar(expressao: texto) -> booleano
que:- Percorre cada caractere; ao abrir “( { [” faz
push
; ao fechar “) } ]” fazpop
e compara tipo. - Em qualquer inconsistência, retorna
false
.
- Percorre cada caractere; ao abrir “( { [” faz
-
Demonstre com as expressões “([])”, “([)]” e “((())”.
5.3: Undo/Redo em Editor de Texto
Contexto: Um editor permite desfazer (undo
) e refazer (redo
) operações de digitação.
Tarefa:
-
Modele duas pilhas:
PilhaUndo
ePilhaRedo
, cada elemento contendo o texto inserido/removido. -
Defina operações:
digitar(acao)
→push
emUndo
, limpaRedo
undo()
→pop
deUndo
epush
emRedo
redo()
→pop
deRedo
epush
emUndo
-
Escreva pseudocódigo para estas funções.
-
Simule: digita “A”, “B”; undo; digita “C”; redo (não deve refazer); undo.
5.4: Avaliação de Expressões Pós-fixadas (RPN)
Contexto: Calculadoras RPN (Reverse Polish Notation) usam pilha para avaliar expressões. Tarefa:
-
Modele
PilhaNum
dedouble
. -
Escreva função
avaliarRPN(tokens: lista<string>) -> número
que:- Ao ler número faz
push
, ao ler operador ( + - * / ) fazpop
duas vezes, aplica epush
resultado.
- Ao ler número faz
-
Dê pseudocódigo e avalie as expressões
["2","3","+","4","*"]
e["5","1","2","+","4","*","+","3","-"]
.
5.5: Pilha de Chamadas de Funções (Call Stack)
Contexto: Em tempo de execução, cada chamada de função é empilhada; ao retornar, desempilha-se. Tarefa:
- Modele
PilhaFrame
onde cadaFrame
contémfuncao
(texto) eretorno
(endereço). - Defina
pushFrame
,popFrame
,topFrame
. - Escreva pseudocódigo que simule as chamadas:
main
→A
→B
→ retorna →C
→ retorna → retorna. - Exiba a pilha após cada operação.
5.6: Conversão de Base Numérica
Contexto: Para converter um número decimal em binário, usa-se pilha para armazenar restos. Tarefa:
-
Modele
PilhaInt
. -
Escreva função
converterParaBase(n: inteiro, base: inteiro) -> string
que:- Enquanto
n > 0
, fazpush(n % base)
en /= base
. - Depois, pop todos os restos para formar a representação.
- Enquanto
-
Demonstre convertendo
n=45
para base2
e16
.
5.7: Verificação de Rotas em Labirinto
Contexto: Um algoritmo de backtracking empilha posições visitadas para explorar caminhos e desempilha ao voltar. Tarefa:
-
Modele
PilhaPosicao
comx, y
. -
Escreva pseudocódigo de DFS que:
- Ao mover para próxima célula válida faz
push
; - Se chega em beco sem saída, faz
pop
até encontrar outra direção.
- Ao mover para próxima célula válida faz
-
Descreva como a pilha cresce e encolhe para um labirinto 3×3 simples.
5.8: Implementação de Undo em Jogo de Desenho
Contexto: Num app de desenho, cada traço (linha) é um objeto empilhado para possibilitar “desfazer”. Tarefa:
- Modele
PilhaTraço
ondeTrazado
guarda coordenadas(x1,y1)-(x2,y2)
. - Defina
adicionarTraco(traco)
,undoTraco()
. - Pseudocódigo para desenhar 5 traços, desfazer 3, adicionar 2 e desfazer 1.
5.9: Avaliação de Notação Infix para Pós-fix
Contexto: Algoritmo Shunting-Yard converte expressão infix para postfix usando uma pilha de operadores. Tarefa:
- Modele
PilhaOp
de operadores+ - * / ( )
. - Escreva pseudocódigo do Shunting-Yard (push em
(
, pop até(
ao encontrar)
etc.). - Converta “3 + 4 * 2 / ( 1 - 5 )”.
5.10: Verificação de Palíndromo
Contexto: Para testar se uma string é palíndroma, empilha-se a primeira metade e compara com a segunda. Tarefa:
-
Modele
PilhaChar
. -
Escreva função
ehPalindromo(s: texto) -> booleano
que:- Empilha
s[0..mid-1]
; - Se
len
ímpar, ignoramid
; - Percorre
mid..end
, pop e compara.
- Empilha
-
Demonstre com “radar”, “level”, “hello”.