Estrutura de Dados: Pilhas (LIFO)
Análise e Implementação de Estruturas Lineares em Vetores Estáticos e Gestão de Memória em Java
O Conceito LIFO e a Estrutura de Pilha
1.1 O que é uma Pilha?
A pilha é uma estrutura de dados linear e restrita que opera sob o protocolo de acesso LIFO (Last-In, First-Out) — o último elemento a entrar na estrutura deve ser obrigatoriamente o primeiro a sair.
1.2 Analogias e Funcionamento
Pense em uma pilha de pratos: se você empilhar três pratos, o último colocado no topo é o primeiro a ser retirado para lavagem. No mundo do software, esse conceito é crucial e está presente em:
- Mecanismo de desfazer/refazer (Undo/Redo - Ctrl+Z): Cada modificação de estado é empilhada para que a última ação possa ser revertida rapidamente.
- Histórico do Navegador: O botão "voltar" nos leva para a última página acessada.
- Pilha de Execução de Funções (Call Stack): A JVM gerencia a execução de métodos em cada thread empilhando frames (quadros de pilha) que contêm variáveis locais e operandos.
Esquema das operações fundamentais: PUSH (Inserir) e POP (Remover) no Topo da Pilha
Vetores Estáticos vs. Coleções Dinâmicas
2.1 O Modelo de Armazenamento Estático
Implementar uma pilha utilizando vetores primitivos (arrays estáticos de tamanho fixo) nos dá total controle sobre o uso de recursos de hardware. Abaixo, analisamos as vantagens e desvantagens dessa escolha arquitetural:
Vantagens Técnicas
- Previsibilidade Temporal: Operações de inserção e remoção ocorrem em complexidade constante O(1), sem necessidade de realocações ou cópia de memória em tempo de execução.
- Localidade de Referência: Os elementos de um array são organizados sequencialmente de acordo com seus índices, proporcionando acesso direto por índice e melhorando consideravelmente a localidade espacial para o Cache do processador.
- Baixo Impacto no Garbage Collector: Como não criamos novos nós dinâmicos a cada inserção, o coletor de lixo não é sobrecarregado.
Desvantagens e Limitações
- Inflexibilidade: O tamanho máximo da pilha precisa ser definido na instanciação e não pode crescer dinamicamente sem recriar toda a estrutura.
- Desperdício de Memória: Se for alocado um tamanho exagerado (ex: capacidade para 1 milhão de elementos) e a pilha armazenar apenas 10 itens, a memória inutilizada continuará reservada.
Gestão de Erros de Pilha: Overflow e Underflow
3.1 O que é Overflow (Estouro)?
Em uma implementação de pilha com capacidade fixa, o erro de Overflow (Estouro) ocorre quando tentamos inserir um elemento e a pilha já está cheia.
3.2 O que é Underflow (Esvaziamento)?
O erro de Underflow ocorre no sentido oposto: quando tentamos remover (pop) ou consultar (top) dados em uma pilha que não possui nenhum elemento guardado (quando o índice topo == -1).
Dica de Projeto Defensivo
Sempre verifique as condições limites antes de alterar o índice do topo. Nunca permita que o índicetopoultrapasse as bordas-1etamanho - 1do array.
Os Métodos Fundamentais de Controle
Para operar uma pilha de maneira segura e encapsulada, definimos 5 métodos fundamentais que formam a sua interface pública:
| Método | Descrição Técnica | Comportamento do Índice |
|---|---|---|
| empilhar(valor) / push | Valida se a pilha está cheia. Se não estiver, adiciona o elemento no topo. | Incrementa topo (topo++) |
| desempilhar() / pop | Valida se a pilha está vazia. Se não estiver, remove/descarta o elemento do topo. | Decrementa topo (topo--) |
| retornarTopo() / peek | Consulta e retorna o elemento atual do topo, sem removê-lo. | Inalterado |
| verificarPilhaVazia() | Verifica se a pilha não possui elementos (topo == -1). | Inalterado |
| verificarPilhaCheia() | Verifica se a pilha atingiu a capacidade limite (topo == tamanho - 1). | Inalterado |
Implementação Completa em Java
Confira o código de referência estruturado com as validações adequadas para evitar problemas de Overflow e Underflow:
public class Pilha {
private int topo; // Controla o índice do elemento que está no topo
private int[] itens; // Vetor que armazena os elementos fisicamente
// Construtor que inicializa a pilha com um tamanho estático
public Pilha(int tamanho) {
this.topo = -1; // -1 indica pilha vazia
this.itens = new int[tamanho]; // Cria o array que armazenará os elementos
}
// Verifica se a pilha está vazia
public boolean verificarPilhaVazia() {
return this.topo == -1;
}
// Verifica se a pilha está cheia
public boolean verificarPilhaCheia() {
return this.topo == this.itens.length - 1;
}
// Adiciona um valor no topo (PUSH) com tratamento preventivo contra Overflow
public void empilhar(int valor) {
if (verificarPilhaCheia()) {
System.out.println("[Erro] Overflow! A pilha está cheia. Não foi possível inserir " + valor);
return;
}
this.topo++;
this.itens[this.topo] = valor;
}
// Remove o elemento do topo (POP) com tratamento contra Underflow
public void desempilhar() {
if (verificarPilhaVazia()) {
System.out.println("[Erro] Underflow! A pilha está vazia. Não é possível remover.");
return;
}
this.topo--; // O decremento descarta logicamente o elemento
}
// Retorna o valor do topo (PEEK) sem removê-lo
public int retornarTopo() {
if (verificarPilhaVazia()) {
throw new RuntimeException("A pilha está vazia. Não há elemento no topo.");
}
return this.itens[this.topo];
}
}
Estudo de Caso Prático: Inversão de Textos
Utilizaremos a pilha que implementamos para criar um inversor de textos (caractere por caractere). Como a pilha inverte o fluxo das operações de entrada, ela é a estrutura perfeita para essa finalidade.
public class AppInversor {
public static void main(String[] args) {
String textoOriginal = "ESTRUTURA DE DADOS";
System.out.println("Original: " + textoOriginal);
// Instanciamos uma pilha com capacidade suficiente
Pilha pilhaLetras = new Pilha(textoOriginal.length());
// Passo 1: Empilhar cada caractere
for (int i = 0; i < textoOriginal.length(); i++) {
char letra = textoOriginal.charAt(i);
pilhaLetras.empilhar((int) letra); // Fazemos cast para int
}
// Passo 2: Desempilhar reconstruindo o texto invertido
StringBuilder textoInvertido = new StringBuilder();
while (!pilhaLetras.verificarPilhaVazia()) {
char letraTopo = (char) pilhaLetras.retornarTopo();
textoInvertido.append(letraTopo);
pilhaLetras.desempilhar();
}
System.out.println("Invertido: " + textoInvertido.toString());
}
}
Prática de Laboratório (Mãos à Obra!)
Desafio Prático: O Validador de Parênteses
Implemente um programa em Java capaz de verificar se uma expressão matemática tem seus parênteses abertos e fechados corretamente utilizando uma pilha.
Especificações do Desafio:
- Regra 1: Varra a string da expressão caractere por caractere.
- Regra 2: Sempre que encontrar um parêntese aberto
(, faça umempilhar(). - Regra 3: Sempre que encontrar um parêntese fechado
), verifique se a pilha está vazia. Se estiver, a expressão é inválida (Underflow). Se não estiver vazia, faça umdesempilhar(). - Regra 4: Ao final da varredura, se a pilha estiver vazia, a expressão está balanceada e correta. Caso reste algum elemento, ela é inválida.
public class ValidadorExpressao {
public static boolean verificarParenteses(String expressao) {
Pilha pilha = new Pilha(expressao.length());
for (int i = 0; i < expressao.length(); i++) {
char c = expressao.charAt(i);
if (c == '(') {
pilha.empilhar(c);
} else if (c == ')') {
if (pilha.verificarPilhaVazia()) {
return false; // Parêntese fechado sem abertura correspondente
}
pilha.desempilhar();
}
}
return pilha.verificarPilhaVazia(); // Expressão é válida se todos foram fechados
}
public static void main(String[] args) {
String expValida = "((a + b) * (c - d))";
String expInvalida = "((a + b) * c))";
System.out.println("Expressão 1 válida? " + verificarParenteses(expValida)); // true
System.out.println("Expressão 2 válida? " + verificarParenteses(expInvalida)); // false
}
}
Fim deste Conteúdo
Aqui encerra-se a nossa análise detalhada sobre estruturas tipo Pilha estática baseadas em vetor. Na próxima aula, avançaremos para o estudo de Filas (FIFO) e suas implementações no ecossistema Java.
Materiais Complementares de Apoio
Slides da Aula: Pilhas
Acesse o slide de apoio didático estruturado em PDF/PowerPoint, contendo toda a parte visual, esquemas de pilha e diagramas de classe UML abordados.
Videoaula Relacionada: PILHA - Java
Assista à explicação detalhada com resoluções de problemas e exemplos práticos de implementação de pilhas.
Repositório de Código Fonte
Explore, acesse e faça o download de todos os códigos-fonte de exemplo e exercícios estruturados nesta trilha.