Professor Muniz Educação & Programação

Trilha: Estrutura de Dados

Voltar aos conteúdos
Apostila de Apoio Didático

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

MÓDULO 1

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 Visual de Pilha: PUSH e POP Esquema das operações fundamentais: PUSH (Inserir) e POP (Remover) no Topo da Pilha
MÓDULO 2

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.
MÓDULO 3

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 índice topo ultrapasse as bordas -1 e tamanho - 1 do array.
MÓDULO 4

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
MÓDULO 5

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.

Inversor de Strings (AppInversor.java)
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());
    }
}
MÓDULO 6

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:
  1. Regra 1: Varra a string da expressão caractere por caractere.
  2. Regra 2: Sempre que encontrar um parêntese aberto (, faça um empilhar().
  3. 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 um desempilhar().
  4. 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.
Gabarito Recomendado de Resolução:
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.

Baixar Slides
Videoaula Relacionada: PILHA - Java

Assista à explicação detalhada com resoluções de problemas e exemplos práticos de implementação de pilhas.

Assistir Vídeo
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.

Abrir Repositório
Voltar aos conteúdos